From 3efc181140004c9910c3bb18203dd4db64762a75 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:30:26 +0300 Subject: [PATCH 01/24] feat(PCL-Duality): add PCL with Duality OB integration --- Cargo.lock | 313 ++++- Cargo.toml | 3 +- contracts/pair_concentrated/src/contract.rs | 1 + contracts/pair_concentrated/src/error.rs | 3 + contracts/pair_concentrated/tests/helper.rs | 1 + .../pair_concentrated_duality/.cargo/config | 6 + .../pair_concentrated_duality/.editorconfig | 11 + .../pair_concentrated_duality/Cargo.toml | 50 + contracts/pair_concentrated_duality/README.md | 1 + .../pair_concentrated_duality_schema.rs | 12 + .../pair_concentrated_duality/rustfmt.toml | 15 + .../pair_concentrated_duality/src/error.rs | 80 ++ .../pair_concentrated_duality/src/execute.rs | 709 +++++++++++ .../src/instantiate.rs | 143 +++ .../pair_concentrated_duality/src/lib.rs | 9 + .../pair_concentrated_duality/src/migrate.rs | 74 ++ .../src/orderbook/consts.rs | 11 + .../src/orderbook/error.rs | 20 + .../src/orderbook/execute.rs | 189 +++ .../src/orderbook/mod.rs | 5 + .../src/orderbook/state.rs | 456 +++++++ .../src/orderbook/utils.rs | 207 +++ .../pair_concentrated_duality/src/queries.rs | 364 ++++++ .../pair_concentrated_duality/src/reply.rs | 49 + .../pair_concentrated_duality/src/state.rs | 10 + .../pair_concentrated_duality/src/utils.rs | 268 ++++ .../tests/common/astroport_wrapper.rs | 268 ++++ .../tests/common/helper.rs | 527 ++++++++ .../tests/common/mod.rs | 4 + .../tests/common/neutron_wrapper.rs | 236 ++++ .../tests/pcl_duality_e2e.rs | 35 + .../tests/pcl_duality_integration.rs | 1117 +++++++++++++++++ contracts/pair_stable/src/contract.rs | 1 + contracts/pair_stable/src/error.rs | 3 + contracts/pair_transmuter/tests/helper.rs | 1 + .../astro_converter_neutron/Cargo.toml | 2 +- packages/astroport/Cargo.toml | 1 + packages/astroport/src/lib.rs | 2 + packages/astroport/src/pair.rs | 8 +- .../src/pair_concentrated_duality.rs | 75 ++ packages/astroport_test/Cargo.toml | 7 +- packages/astroport_test/src/coins.rs | 15 +- packages/astroport_test/src/lib.rs | 1 + packages/astroport_test/src/modules/mod.rs | 1 + .../src/modules/neutron_stargate.rs | 207 +++ 45 files changed, 5489 insertions(+), 32 deletions(-) create mode 100644 contracts/pair_concentrated_duality/.cargo/config create mode 100644 contracts/pair_concentrated_duality/.editorconfig create mode 100644 contracts/pair_concentrated_duality/Cargo.toml create mode 100644 contracts/pair_concentrated_duality/README.md create mode 100644 contracts/pair_concentrated_duality/examples/pair_concentrated_duality_schema.rs create mode 100644 contracts/pair_concentrated_duality/rustfmt.toml create mode 100644 contracts/pair_concentrated_duality/src/error.rs create mode 100644 contracts/pair_concentrated_duality/src/execute.rs create mode 100644 contracts/pair_concentrated_duality/src/instantiate.rs create mode 100644 contracts/pair_concentrated_duality/src/lib.rs create mode 100644 contracts/pair_concentrated_duality/src/migrate.rs create mode 100644 contracts/pair_concentrated_duality/src/orderbook/consts.rs create mode 100644 contracts/pair_concentrated_duality/src/orderbook/error.rs create mode 100644 contracts/pair_concentrated_duality/src/orderbook/execute.rs create mode 100644 contracts/pair_concentrated_duality/src/orderbook/mod.rs create mode 100644 contracts/pair_concentrated_duality/src/orderbook/state.rs create mode 100644 contracts/pair_concentrated_duality/src/orderbook/utils.rs create mode 100644 contracts/pair_concentrated_duality/src/queries.rs create mode 100644 contracts/pair_concentrated_duality/src/reply.rs create mode 100644 contracts/pair_concentrated_duality/src/state.rs create mode 100644 contracts/pair_concentrated_duality/src/utils.rs create mode 100644 contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs create mode 100644 contracts/pair_concentrated_duality/tests/common/helper.rs create mode 100644 contracts/pair_concentrated_duality/tests/common/mod.rs create mode 100644 contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs create mode 100644 contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs create mode 100644 contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs create mode 100644 packages/astroport/src/pair_concentrated_duality.rs create mode 100644 packages/astroport_test/src/modules/neutron_stargate.rs diff --git a/Cargo.lock b/Cargo.lock index 9d96d0b39..1f77dd4f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,7 +77,7 @@ dependencies = [ "cosmwasm-std", "cw-utils 1.0.3", "cw2 1.1.2", - "neutron-sdk", + "neutron-sdk 0.9.0", ] [[package]] @@ -263,7 +263,7 @@ dependencies = [ "astro-token-converter", "astroport 5.5.0", "astroport-factory 1.9.0", - "astroport-native-coin-registry", + "astroport-native-coin-registry 1.1.0", "astroport-pair 2.1.0", "astroport-pair-stable", "astroport-test", @@ -288,7 +288,7 @@ dependencies = [ "astro-satellite-package", "astroport 4.0.3", "astroport-factory 1.9.0", - "astroport-native-coin-registry", + "astroport-native-coin-registry 1.1.0", "astroport-pair 2.1.0", "astroport-pair-stable", "astroport-test", @@ -315,6 +315,45 @@ dependencies = [ "thiserror", ] +[[package]] +name = "astroport-native-coin-registry" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94d9ed2ada833c79721cde3ff0f2d3c150a5f6c723d72bf6d446f513b71ceba" +dependencies = [ + "astroport 5.2.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "itertools 0.12.1", + "thiserror", +] + +[[package]] +name = "astroport-neutron-sdk" +version = "0.11.0-cosmwasm-v1" +source = "git+https://github.com/epanchee/neutron-sdk?branch=backport/cosmwasm_v1#2c94553f36230ecaf5ab8f4c5ce856fa64d8cc59" +dependencies = [ + "bech32 0.9.1", + "chrono", + "cosmos-sdk-proto 0.20.0", + "cosmwasm-schema", + "cosmwasm-std", + "neutron-std-derive", + "prost 0.12.6", + "prost-types 0.12.6", + "protobuf 3.3.0", + "schemars", + "serde", + "serde-cw-value", + "serde-json-wasm 1.0.1", + "serde_json", + "speedate", + "tendermint-proto 0.34.1", + "thiserror", +] + [[package]] name = "astroport-oracle" version = "2.1.2" @@ -322,7 +361,7 @@ dependencies = [ "anyhow", "astroport 3.12.2", "astroport-factory 1.9.0", - "astroport-native-coin-registry", + "astroport-native-coin-registry 1.1.0", "astroport-pair 2.1.0", "astroport-pair-stable", "astroport-test", @@ -384,7 +423,7 @@ dependencies = [ "astroport-circular-buffer 0.2.0", "astroport-factory 1.9.0", "astroport-incentives", - "astroport-native-coin-registry", + "astroport-native-coin-registry 1.1.0", "astroport-pcl-common", "astroport-test", "astroport-tokenfactory-tracker 2.0.0", @@ -401,6 +440,28 @@ dependencies = [ "thiserror", ] +[[package]] +name = "astroport-pair-concentrated-duality" +version = "4.1.0" +dependencies = [ + "anyhow", + "astroport 5.5.0", + "astroport-factory 1.9.0", + "astroport-native-coin-registry 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "astroport-neutron-sdk", + "astroport-pcl-common", + "astroport-test", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "derivative", + "itertools 0.12.1", + "neutron-test-tube 4.0.1", + "thiserror", +] + [[package]] name = "astroport-pair-converter" version = "1.0.0" @@ -433,7 +494,7 @@ dependencies = [ "astroport-circular-buffer 0.2.0", "astroport-factory 1.9.0", "astroport-incentives", - "astroport-native-coin-registry", + "astroport-native-coin-registry 1.1.0", "astroport-test", "cosmwasm-schema", "cosmwasm-std", @@ -457,7 +518,7 @@ dependencies = [ "anyhow", "astroport 5.5.0", "astroport-factory 1.9.0", - "astroport-native-coin-registry", + "astroport-native-coin-registry 1.1.0", "astroport-test", "cosmwasm-schema", "cosmwasm-std", @@ -556,14 +617,19 @@ version = "0.1.0" dependencies = [ "anyhow", "astroport 5.5.0", + "astroport-factory 1.9.0", + "astroport-neutron-sdk", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 1.0.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", + "cw20-base 1.1.2", "itertools 0.12.1", + "prost 0.12.6", "schemars", "serde", + "sha2 0.10.8", ] [[package]] @@ -590,8 +656,8 @@ dependencies = [ "cw-multi-test 0.20.0", "cw-storage-plus 1.2.0", "cw2 1.1.2", - "neutron-sdk", - "neutron-test-tube", + "neutron-sdk 0.8.0", + "neutron-test-tube 0.1.0", "test-tube", "thiserror", ] @@ -638,7 +704,7 @@ dependencies = [ "cosmwasm-std", "cw1-whitelist", "cw2 1.1.2", - "neutron-sdk", + "neutron-sdk 0.8.0", "thiserror", ] @@ -668,6 +734,17 @@ dependencies = [ "syn 2.0.68", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -729,6 +806,29 @@ dependencies = [ "crunchy 0.1.6", ] +[[package]] +name = "bindgen" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", +] + [[package]] name = "bindgen" version = "0.69.4" @@ -738,7 +838,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.10.5", "lazy_static", "lazycell", "log", @@ -890,6 +990,30 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex", + "indexmap 1.9.3", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -966,12 +1090,11 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.5.5" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd50718a2b6830ce9eb5d465de5a018a12e71729d66b70807ce97e6dd14f931d" +checksum = "58535cbcd599b3c193e3967c8292fe1dbbb5de7c2a2d87380661091dd4744044" dependencies = [ "digest 0.10.7", - "ecdsa", "ed25519-zebra", "k256", "rand_core 0.6.4", @@ -1537,6 +1660,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1785,7 +1921,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -1822,6 +1958,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1886,6 +2031,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.29" @@ -1958,6 +2109,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.2.6" @@ -2047,9 +2208,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", @@ -2174,7 +2335,28 @@ dependencies = [ "cosmwasm-std", "prost 0.12.6", "prost-types 0.12.6", - "protobuf 3.5.0", + "protobuf 3.3.0", + "schemars", + "serde", + "serde-json-wasm 1.0.1", + "speedate", + "tendermint-proto 0.34.1", + "thiserror", +] + +[[package]] +name = "neutron-sdk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd5796f1b0135847c804025ee48b5e20756458d191ce889ba910ca4b16509b29" +dependencies = [ + "bech32 0.9.1", + "cosmos-sdk-proto 0.20.0", + "cosmwasm-schema", + "cosmwasm-std", + "prost 0.12.6", + "prost-types 0.12.6", + "protobuf 3.3.0", "schemars", "serde", "serde-json-wasm 1.0.1", @@ -2183,16 +2365,28 @@ dependencies = [ "thiserror", ] +[[package]] +name = "neutron-std-derive" +version = "0.20.1" +source = "git+https://github.com/epanchee/neutron-sdk?branch=backport/cosmwasm_v1#2c94553f36230ecaf5ab8f4c5ce856fa64d8cc59" +dependencies = [ + "itertools 0.10.5", + "proc-macro2", + "prost-types 0.12.6", + "quote", + "syn 1.0.109", +] + [[package]] name = "neutron-test-tube" version = "0.1.0" source = "git+https://github.com/j0nl1/neutron-test-tube#7b478ebc5e8bd4a6fb5df8655a5d4028d90d00d9" dependencies = [ "base64", - "bindgen", + "bindgen 0.69.4", "cosmrs", "cosmwasm-std", - "neutron-sdk", + "neutron-sdk 0.8.0", "prost 0.12.6", "serde", "serde_json", @@ -2200,6 +2394,25 @@ dependencies = [ "thiserror", ] +[[package]] +name = "neutron-test-tube" +version = "4.0.1" +source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/cosmwasm-v1#518172e3d0fc52f16b291864bc2bf0a4d9c56eb0" +dependencies = [ + "astroport-neutron-sdk", + "base64", + "bindgen 0.60.1", + "cosmos-sdk-proto 0.20.0", + "cosmrs", + "cosmwasm-std", + "hex", + "prost 0.12.6", + "serde", + "serde_json", + "test-tube-ntrn", + "thiserror", +] + [[package]] name = "nom" version = "7.1.3" @@ -2243,7 +2456,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -2274,6 +2487,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + [[package]] name = "osmosis-std" version = "0.21.0" @@ -2332,6 +2551,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "peg" version = "0.8.3" @@ -2498,7 +2723,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.68", @@ -2533,9 +2758,9 @@ dependencies = [ [[package]] name = "protobuf" -version = "3.5.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df67496db1a89596beaced1579212e9b7c53c22dca1d9745de00ead76573d514" +checksum = "b65f4a8ec18723a734e5dc09c173e0abf9690432da5340285d536edcb4dac190" dependencies = [ "once_cell", "protobuf-support", @@ -2544,9 +2769,9 @@ dependencies = [ [[package]] name = "protobuf-support" -version = "3.5.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e2d30ab1878b2e72d1e2fc23ff5517799c9929e2cf81a8516f9f4dcf2b9cf3" +checksum = "6872f4d4f4b98303239a2b5838f5bbbb77b01ffc892d627957f37a22d7cfe69c" dependencies = [ "thiserror", ] @@ -3253,6 +3478,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strum" version = "0.25.0" @@ -3477,6 +3708,15 @@ dependencies = [ "walkdir", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "test-case" version = "3.3.1" @@ -3524,6 +3764,27 @@ dependencies = [ "thiserror", ] +[[package]] +name = "test-tube-ntrn" +version = "0.1.0" +source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/cosmwasm-v1#518172e3d0fc52f16b291864bc2bf0a4d9c56eb0" +dependencies = [ + "base64", + "cosmos-sdk-proto 0.20.0", + "cosmrs", + "cosmwasm-std", + "prost 0.12.6", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" + [[package]] name = "thiserror" version = "1.0.61" diff --git a/Cargo.toml b/Cargo.toml index 7c10ec11d..7ccbeb6ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "contracts/pair_concentrated", "contracts/pair_astro_converter", "contracts/pair_transmuter", + "contracts/pair_concentrated_duality", # "contracts/pair_concentrated_inj", TODO: rewrite OB liquidity deployment "contracts/pair_xyk_sale_tax", "contracts/router", @@ -24,7 +25,7 @@ thiserror = "1.0" itertools = "0.12" cosmwasm-schema = "1.5" cw-utils = "1" -astroport = { path = "./packages/astroport", version = "5.4.0" } +astroport = { path = "./packages/astroport", version = "5.5.0" } [profile.release] opt-level = "z" diff --git a/contracts/pair_concentrated/src/contract.rs b/contracts/pair_concentrated/src/contract.rs index 477e63118..eb3a030b6 100644 --- a/contracts/pair_concentrated/src/contract.rs +++ b/contracts/pair_concentrated/src/contract.rs @@ -367,6 +367,7 @@ pub fn execute( .map_err(Into::into) } ExecuteMsg::WithdrawLiquidity { assets, .. } => withdraw_liquidity(deps, env, info, assets), + _ => Err(ContractError::NotSupported {}), } } diff --git a/contracts/pair_concentrated/src/error.rs b/contracts/pair_concentrated/src/error.rs index 304254cec..b4e0cd512 100644 --- a/contracts/pair_concentrated/src/error.rs +++ b/contracts/pair_concentrated/src/error.rs @@ -69,4 +69,7 @@ pub enum ContractError { #[error("Slippage is more than expected: received {0}, expected {1} LP tokens")] ProvideSlippageViolation(Uint128, Uint128), + + #[error("Operation is not supported")] + NotSupported {}, } diff --git a/contracts/pair_concentrated/tests/helper.rs b/contracts/pair_concentrated/tests/helper.rs index 36743930d..6b62ec194 100644 --- a/contracts/pair_concentrated/tests/helper.rs +++ b/contracts/pair_concentrated/tests/helper.rs @@ -174,6 +174,7 @@ impl Helper { owner, )) } + TestCoin::NativePrecise(..) => unimplemented!(), }; (coin, asset_info) }) diff --git a/contracts/pair_concentrated_duality/.cargo/config b/contracts/pair_concentrated_duality/.cargo/config new file mode 100644 index 000000000..379205b72 --- /dev/null +++ b/contracts/pair_concentrated_duality/.cargo/config @@ -0,0 +1,6 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +wasm-debug = "build --target wasm32-unknown-unknown" +unit-test = "test --lib" +integration-test = "test --test integration" +schema = "run --example pair_concentrated_schema" diff --git a/contracts/pair_concentrated_duality/.editorconfig b/contracts/pair_concentrated_duality/.editorconfig new file mode 100644 index 000000000..3d36f20b1 --- /dev/null +++ b/contracts/pair_concentrated_duality/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.rs] +indent_size = 4 diff --git a/contracts/pair_concentrated_duality/Cargo.toml b/contracts/pair_concentrated_duality/Cargo.toml new file mode 100644 index 000000000..182f662a1 --- /dev/null +++ b/contracts/pair_concentrated_duality/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "astroport-pair-concentrated-duality" +version = "4.1.0" +authors = ["Astroport"] +edition = "2021" +description = "The Astroport concentrated liquidity pair with Duality orderbook integration" +license = "GPL-3.0-only" +repository = "https://github.com/astroport-fi/astroport" +homepage = "https://astroport.fi" + +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] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +test-tube = ["neutron-test-tube"] +library = [] + +[dependencies] +cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1"] } +cw-storage-plus = { workspace = true } +thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +itertools = { workspace = true } +cw-utils = { workspace = true } +cw2 = { workspace = true } + +#neutron-sdk = { package = "neutron-sdk", git = "https://github.com/neutron-org/neutron-sdk", branch = "feat/remove-stargate" } +neutron-sdk = { package = "astroport-neutron-sdk", git = "https://github.com/epanchee/neutron-sdk", branch = "backport/cosmwasm_v1" } +astroport = { path = "../../packages/astroport", version = "5.5", features = ["duality"] } +astroport-pcl-common = { path = "../../packages/astroport_pcl_common", version = "2" } + +neutron-test-tube = { git = "https://github.com/epanchee/neutron-test-tube-new", optional = true, branch = "feat/cosmwasm-v1" } + +[dev-dependencies] +astroport-native-coin-registry = "1.1" +astroport-test = { path = "../../packages/astroport_test", version = "0.1.0" } +astroport-factory = { path = "../factory", version = "1.8" } +anyhow = "1.0" +derivative = "2.2" \ No newline at end of file diff --git a/contracts/pair_concentrated_duality/README.md b/contracts/pair_concentrated_duality/README.md new file mode 100644 index 000000000..c2f8fe093 --- /dev/null +++ b/contracts/pair_concentrated_duality/README.md @@ -0,0 +1 @@ +# Astroport Concentrated Liquidity Pair with Duality Orderbook integration \ No newline at end of file diff --git a/contracts/pair_concentrated_duality/examples/pair_concentrated_duality_schema.rs b/contracts/pair_concentrated_duality/examples/pair_concentrated_duality_schema.rs new file mode 100644 index 000000000..536a9b2e4 --- /dev/null +++ b/contracts/pair_concentrated_duality/examples/pair_concentrated_duality_schema.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::write_api; + +use astroport::pair::{ExecuteMsgExt, InstantiateMsg, QueryMsg}; +use astroport::pair_concentrated_duality::DualityPairMsg; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsgExt + } +} diff --git a/contracts/pair_concentrated_duality/rustfmt.toml b/contracts/pair_concentrated_duality/rustfmt.toml new file mode 100644 index 000000000..93832eece --- /dev/null +++ b/contracts/pair_concentrated_duality/rustfmt.toml @@ -0,0 +1,15 @@ +# stable +newline_style = "Unix" +hard_tabs = false +tab_spaces = 4 + +# unstable... should we require `rustup run nightly cargo fmt` ? +# or just update the style guide when they are stable? +#fn_single_line = true +#format_code_in_doc_comments = true +#overflow_delimited_expr = true +#reorder_impl_items = true +#struct_field_align_threshold = 20 +#struct_lit_single_line = true +#report_todo = "Always" + diff --git a/contracts/pair_concentrated_duality/src/error.rs b/contracts/pair_concentrated_duality/src/error.rs new file mode 100644 index 000000000..ebef21022 --- /dev/null +++ b/contracts/pair_concentrated_duality/src/error.rs @@ -0,0 +1,80 @@ +use cosmwasm_std::{ConversionOverflowError, OverflowError, StdError, Uint128}; +use cw_utils::{ParseReplyError, PaymentError}; +use thiserror::Error; + +use astroport::asset::MINIMUM_LIQUIDITY_AMOUNT; +use astroport::pair::MAX_FEE_SHARE_BPS; +use astroport_pcl_common::error::PclError; + +use crate::orderbook::error::OrderbookError; + +/// This enum describes pair contract errors +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + PclError(#[from] PclError), + + #[error("{0}")] + OrderbookError(#[from] OrderbookError), + + #[error("{0}")] + ConversionOverflowError(#[from] ConversionOverflowError), + + #[error("{0}")] + OverflowError(#[from] OverflowError), + + #[error("{0}")] + ParseReplyError(#[from] ParseReplyError), + + #[error("{0}")] + PaymentError(#[from] PaymentError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("You need to provide init params")] + InitParamsNotFound {}, + + #[error("The asset {0} does not belong to the pair")] + InvalidAsset(String), + + #[error("Pair is not registered in the factory. Only swap and withdraw are allowed")] + PairIsNotRegistered {}, + + #[error("Invalid number of assets. This pair supports only {0} assets")] + InvalidNumberOfAssets(usize), + + #[error("Initial provide can not be one-sided")] + InvalidZeroAmount {}, + + #[error("Initial liquidity must be more than {}", MINIMUM_LIQUIDITY_AMOUNT)] + MinimumLiquidityAmountError {}, + + #[error( + "Fee share is 0 or exceeds maximum allowed value of {} bps", + MAX_FEE_SHARE_BPS + )] + FeeShareOutOfBounds {}, + + #[error("Failed to parse or process reply message")] + FailedToParseReply {}, + + #[error("cw20 tokens are not supported")] + NonNativeAsset {}, + + #[error("Slippage is more than expected: received {0}, expected {1} LP tokens")] + ProvideSlippageViolation(Uint128, Uint128), + + #[error("Received {received} {asset_name} but expected {expected}")] + WithdrawSlippageViolation { + asset_name: String, + received: Uint128, + expected: Uint128, + }, + + #[error("Wrong asset length: expected {expected}, actual {actual}")] + WrongAssetLength { expected: usize, actual: usize }, +} diff --git a/contracts/pair_concentrated_duality/src/execute.rs b/contracts/pair_concentrated_duality/src/execute.rs new file mode 100644 index 000000000..45104a911 --- /dev/null +++ b/contracts/pair_concentrated_duality/src/execute.rs @@ -0,0 +1,709 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + attr, coin, ensure, ensure_eq, from_json, Addr, Binary, Decimal, Decimal256, DepsMut, Env, + MessageInfo, Response, StdError, StdResult, Uint128, +}; +use cw_utils::must_pay; +use itertools::Itertools; + +use astroport::asset::{ + addr_opt_validate, Asset, AssetInfo, AssetInfoExt, CoinsExt, MINIMUM_LIQUIDITY_AMOUNT, +}; +use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; +use astroport::cosmwasm_ext::{DecimalToInteger, IntegerToDecimal}; +use astroport::pair::{ExecuteMsgExt, FeeShareConfig, MAX_FEE_SHARE_BPS, MIN_TRADE_SIZE}; +use astroport::pair_concentrated::ConcentratedPoolUpdateParams; +use astroport::pair_concentrated_duality::DualityPairMsg; +use astroport::querier::{query_factory_config, query_fee_info, query_native_supply}; +use astroport::token_factory::tf_burn_msg; +use astroport_pcl_common::state::Precisions; +use astroport_pcl_common::utils::{ + accumulate_prices, assert_max_spread, before_swap_check, calc_last_prices, compute_swap, + get_share_in_assets, mint_liquidity_token_message, +}; +use astroport_pcl_common::{calc_d, get_xcp}; + +use crate::error::ContractError; +use crate::instantiate::LP_TOKEN_PRECISION; +use crate::orderbook::execute::{process_cumulative_trade, sync_pool_with_orderbook}; +use crate::orderbook::state::OrderbookState; +use crate::state::{CONFIG, OWNERSHIP_PROPOSAL}; +use crate::utils::{ + calculate_shares, ensure_min_assets_to_receive, get_assets_with_precision, + query_contract_balances, query_pools, +}; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsgExt, +) -> Result { + match msg { + ExecuteMsgExt::ProvideLiquidity { + assets, + slippage_tolerance, + auto_stake, + receiver, + min_lp_to_receive, + } => provide_liquidity( + deps, + env, + info, + assets, + slippage_tolerance, + auto_stake, + receiver, + min_lp_to_receive, + ), + ExecuteMsgExt::Swap { + offer_asset, + belief_price, + max_spread, + to, + .. + } => { + offer_asset.assert_sent_native_token_balance(&info)?; + + let config = CONFIG.load(deps.storage)?; + + if !config.pair_info.asset_infos.contains(&offer_asset.info) { + return Err(ContractError::InvalidAsset(offer_asset.info.to_string())); + } + + let to_addr = addr_opt_validate(deps.api, &to)?; + + swap( + deps, + env, + info.sender, + offer_asset, + belief_price, + max_spread, + to_addr, + ) + } + ExecuteMsgExt::WithdrawLiquidity { + assets, + min_assets_to_receive, + } => withdraw_liquidity(deps, env, info, assets, min_assets_to_receive), + ExecuteMsgExt::UpdateConfig { params } => update_config(deps, env, info, params), + ExecuteMsgExt::Custom(duality_msg) => process_custom_msgs(deps, env, info, duality_msg), + ExecuteMsgExt::ProposeNewOwner { owner, expires_in } => { + let config = CONFIG.load(deps.storage)?; + let factory_config = query_factory_config(&deps.querier, config.factory_addr)?; + + propose_new_owner( + deps, + info, + env, + owner, + expires_in, + config.owner.unwrap_or(factory_config.owner), + OWNERSHIP_PROPOSAL, + ) + .map_err(Into::into) + } + ExecuteMsgExt::DropOwnershipProposal {} => { + let config = CONFIG.load(deps.storage)?; + let factory_config = query_factory_config(&deps.querier, config.factory_addr)?; + + drop_ownership_proposal( + deps, + info, + config.owner.unwrap_or(factory_config.owner), + OWNERSHIP_PROPOSAL, + ) + .map_err(Into::into) + } + ExecuteMsgExt::ClaimOwnership {} => { + claim_ownership(deps, info, env, OWNERSHIP_PROPOSAL, |deps, new_owner| { + CONFIG + .update::<_, StdError>(deps.storage, |mut config| { + config.owner = Some(new_owner); + Ok(config) + }) + .map(|_| ()) + }) + .map_err(Into::into) + } + _ => unimplemented!("Unsupported message"), + } +} + +/// Provides liquidity in the pair with the specified input parameters. +/// +/// * **assets** is an array with assets available in the pool. +/// +/// * **slippage_tolerance** is an optional parameter which is used to specify how much +/// the pool price can move until the provide liquidity transaction goes through. +/// +/// * **auto_stake** is an optional parameter which determines whether the LP tokens minted after +/// liquidity provision are automatically staked in the Generator contract on behalf of the LP token receiver. +/// +/// * **receiver** is an optional parameter which defines the receiver of the LP tokens. +/// If no custom receiver is specified, the pair will mint LP tokens for the function caller. +/// +#[allow(clippy::too_many_arguments)] +pub fn provide_liquidity( + deps: DepsMut, + env: Env, + info: MessageInfo, + mut assets: Vec, + slippage_tolerance: Option, + auto_stake: Option, + receiver: Option, + min_lp_to_receive: Option, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)? + .to_decimal256(LP_TOKEN_PRECISION)?; + + let precisions = Precisions::new(deps.storage)?; + + let mut ob_state = OrderbookState::load(deps.storage)?; + + // This call fetches possible cumulative trade and caches orderbook balances in ob_state + let maybe_cumulative_trade = + ob_state.fetch_cumulative_trade(deps.as_ref(), &env.contract.address, &precisions)?; + + let mut pools = query_pools( + deps.as_ref(), + &env.contract.address, + &config, + &precisions, + &ob_state, + )?; + + let old_real_price = config.pool_state.price_state.last_price; + + let deposits = + get_assets_with_precision(deps.as_ref(), &config, &mut assets, &pools, &precisions)?; + + info.funds + .assert_coins_properly_sent(&assets, &config.pair_info.asset_infos)?; + + let mut mint_lp_messages = vec![]; + for (i, pool) in pools.iter_mut().enumerate() { + match &pool.info { + AssetInfo::Token { .. } => unreachable!("cw20 tokens not supported"), + AssetInfo::NativeToken { .. } => { + // If the asset is native token, the pool balance is already increased + // To calculate the total amount of deposits properly, we should subtract the user deposit from the pool + pool.amount = pool.amount.checked_sub(deposits[i])?; + } + } + } + + // Process all filled orders as one cumulative trade; send maker fees; repeg PCL + let response = if let Some(cumulative_trade) = maybe_cumulative_trade { + // This non-trivial array of mutable refs allows us to keep balances updated + // considering sent maker and share fees + let mut balances = pools + .iter_mut() + .map(|asset| &mut asset.amount) + .collect_vec(); + + process_cumulative_trade( + deps.querier, + &env, + &cumulative_trade, + &mut config, + &mut balances, + &precisions, + None, + )? + } else { + Response::default() + }; + + let (share_uint128, slippage) = calculate_shares( + &env, + &mut config, + &pools, + total_share, + &deposits, + slippage_tolerance, + )?; + + if total_share.is_zero() { + mint_lp_messages.extend(mint_liquidity_token_message( + deps.querier, + &config, + &env.contract.address, + &env.contract.address, + MINIMUM_LIQUIDITY_AMOUNT, + false, + )?); + } + + let min_amount_lp = min_lp_to_receive.unwrap_or_default(); + ensure!( + share_uint128 >= min_amount_lp, + ContractError::ProvideSlippageViolation(share_uint128, min_amount_lp) + ); + + // Mint LP tokens for the sender or for the receiver (if set) + let receiver = addr_opt_validate(deps.api, &receiver)?.unwrap_or_else(|| info.sender.clone()); + let auto_stake = auto_stake.unwrap_or(false); + mint_lp_messages.extend(mint_liquidity_token_message( + deps.querier, + &config, + &env.contract.address, + &receiver, + share_uint128, + auto_stake, + )?); + + accumulate_prices(&env, &mut config, old_real_price); + + // Reconcile orders + // Adding deposits back to the pool balances + let balances = pools + .iter() + .zip(deposits.iter()) + .map(|(asset, deposit)| asset.amount + deposit) + .collect_vec(); + let cancel_msgs = ob_state.cancel_orders(&env.contract.address); + let order_msgs = ob_state.deploy_orders(&env, &config, &balances, &precisions)?; + + CONFIG.save(deps.storage, &config)?; + + let submsgs = + ob_state.flatten_msgs_and_add_callback(&[mint_lp_messages, cancel_msgs, order_msgs]); + + Ok(response.add_submessages(submsgs).add_attributes([ + attr("action", "provide_liquidity"), + attr("sender", info.sender), + attr("receiver", receiver), + attr("assets", format!("{}, {}", &assets[0], &assets[1])), + attr("share", share_uint128), + attr("slippage", slippage.to_string()), + ])) +} + +/// Withdraw liquidity from the pool. +/// +/// * **sender** address that will receive assets back from the pair contract +/// +/// * **amount** amount of provided LP tokens +/// +/// * **assets** defines number of coins a user wants to withdraw per each asset. +fn withdraw_liquidity( + deps: DepsMut, + env: Env, + info: MessageInfo, + assets: Vec, + min_assets_to_receive: Option>, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + + let amount = must_pay(&info, &config.pair_info.liquidity_token)?; + + let mut ob_state = OrderbookState::load(deps.storage)?; + + let precisions = Precisions::new(deps.storage)?; + + // This call fetches possible cumulative trade and caches orderbook balances in ob_state + let maybe_cumulative_trade = + ob_state.fetch_cumulative_trade(deps.as_ref(), &env.contract.address, &precisions)?; + + let mut pools = query_pools( + deps.as_ref(), + &env.contract.address, + &config, + &precisions, + &ob_state, + )?; + + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)?; + + // Process all filled orders as one cumulative trade; send maker fees; repeg PCL + let response = if let Some(cumulative_trade) = maybe_cumulative_trade { + // This non-trivial array of mutable refs allows us to keep balances updated + // considering sent maker and share fees + let mut balances = pools + .iter_mut() + .map(|asset| &mut asset.amount) + .collect_vec(); + + process_cumulative_trade( + deps.querier, + &env, + &cumulative_trade, + &mut config, + &mut balances, + &precisions, + None, + )? + } else { + Response::default() + }; + + // In any case, we cancel all orders + let cancel_msgs = ob_state.cancel_orders(&env.contract.address); + + let refund_assets = if assets.is_empty() { + // Usual withdraw (balanced) + get_share_in_assets(&pools, amount.saturating_sub(Uint128::one()), total_share) + } else { + return Err(StdError::generic_err("Imbalanced withdraw is currently disabled").into()); + }; + + let mut xs = pools.iter().map(|a| a.amount).collect_vec(); + + // Reflect balance changes after withdrawal + xs[0] -= refund_assets[0].amount; + xs[1] -= refund_assets[1].amount; + + let contract_balances = + query_contract_balances(deps.querier, &env.contract.address, &config, &precisions)?; + // If the contract does not have enough liquidity - do not deploy new orders + let enough_liq_cond = refund_assets[0].amount <= contract_balances[0].amount + && refund_assets[1].amount <= contract_balances[1].amount; + let order_msgs = if enough_liq_cond { + ob_state.deploy_orders(&env, &config, &xs, &precisions)? + } else { + vec![] + }; + + // decrease XCP + xs[1] *= config.pool_state.price_state.price_scale; + let amp_gamma = config.pool_state.get_amp_gamma(&env); + let d = calc_d(&xs, &_gamma)?; + config.pool_state.price_state.xcp_profit_real = + get_xcp(d, config.pool_state.price_state.price_scale) + / (total_share - amount).to_decimal256(LP_TOKEN_PRECISION)?; + + let refund_assets = refund_assets + .into_iter() + .map(|asset| { + let prec = precisions.get_precision(&asset.info).unwrap(); + + Ok(Asset { + info: asset.info, + amount: asset.amount.to_uint(prec)?, + }) + }) + .collect::>>()?; + + ensure_min_assets_to_receive(&config, &refund_assets, min_assets_to_receive)?; + + let mut withdraw_messages = refund_assets + .iter() + .cloned() + .map(|asset| asset.into_msg(&info.sender)) + .collect::>>()?; + withdraw_messages.push(tf_burn_msg( + env.contract.address, + coin(amount.u128(), &config.pair_info.liquidity_token), + )); + + CONFIG.save(deps.storage, &config)?; + + let submsgs = + ob_state.flatten_msgs_and_add_callback(&[cancel_msgs, withdraw_messages, order_msgs]); + + Ok(response.add_submessages(submsgs).add_attributes([ + attr("action", "withdraw_liquidity"), + attr("sender", info.sender), + attr("withdrawn_share", amount), + attr("refund_assets", refund_assets.iter().join(", ")), + ])) +} + +/// Performs an swap operation with the specified parameters. The trader must approve the +/// pool contract to transfer offer assets from their wallet. +/// +/// * **sender** is the sender of the swap operation. +/// +/// * **offer_asset** proposed asset for swapping. +/// +/// * **belief_price** is used to calculate the maximum swap spread. +/// +/// * **max_spread** sets the maximum spread of the swap operation. +/// +/// * **to** sets the recipient of the swap operation. +fn swap( + deps: DepsMut, + env: Env, + sender: Addr, + offer_asset: Asset, + belief_price: Option, + max_spread: Option, + to: Option, +) -> Result { + let precisions = Precisions::new(deps.storage)?; + let offer_asset_prec = precisions.get_precision(&offer_asset.info)?; + let offer_asset_dec = offer_asset.to_decimal_asset(offer_asset_prec)?; + let mut config = CONFIG.load(deps.storage)?; + + let mut ob_state = OrderbookState::load(deps.storage)?; + + // This call fetches possible cumulative trade and caches orderbook balances in ob_state + let maybe_cumulative_trade = + ob_state.fetch_cumulative_trade(deps.as_ref(), &env.contract.address, &precisions)?; + + let mut pools = query_pools( + deps.as_ref(), + &env.contract.address, + &config, + &precisions, + &ob_state, + )?; + + // Get fee info from the factory + let fee_info = query_fee_info( + &deps.querier, + &config.factory_addr, + config.pair_info.pair_type.clone(), + )?; + + // Process all filled orders as one cumulative trade; send maker fees; repeg PCL + let response = if let Some(cumulative_trade) = maybe_cumulative_trade { + // This non-trivial array of mutable refs allows us to keep balances updated + // considering sent maker and share fees + let mut balances = pools + .iter_mut() + .map(|asset| &mut asset.amount) + .collect_vec(); + + process_cumulative_trade( + deps.querier, + &env, + &cumulative_trade, + &mut config, + &mut balances, + &precisions, + Some(&fee_info), + )? + } else { + Response::default() + }; + + let (offer_ind, _) = pools + .iter() + .find_position(|asset| asset.info == offer_asset_dec.info) + .ok_or_else(|| ContractError::InvalidAsset(offer_asset_dec.info.to_string()))?; + let ask_ind = 1 ^ offer_ind; + let ask_asset_prec = precisions.get_precision(&pools[ask_ind].info)?; + + pools[offer_ind].amount -= offer_asset_dec.amount; + + before_swap_check(&pools, offer_asset_dec.amount)?; + + let mut xs = pools.iter().map(|asset| asset.amount).collect_vec(); + let old_real_price = calc_last_prices(&xs, &config, &env)?; + + let mut maker_fee_share = Decimal256::zero(); + if fee_info.fee_address.is_some() { + maker_fee_share = fee_info.maker_fee_rate.into(); + } + // If this pool is configured to share fees + let mut share_fee_share = Decimal256::zero(); + if let Some(fee_share) = &config.fee_share { + share_fee_share = Decimal256::from_ratio(fee_share.bps, 10000u16); + } + + let swap_result = compute_swap( + &xs, + offer_asset_dec.amount, + ask_ind, + &config, + &env, + maker_fee_share, + share_fee_share, + )?; + xs[offer_ind] += offer_asset_dec.amount; + xs[ask_ind] -= swap_result.dy + swap_result.maker_fee + swap_result.share_fee; + + let return_amount = swap_result.dy.to_uint(ask_asset_prec)?; + let spread_amount = swap_result.spread_fee.to_uint(ask_asset_prec)?; + assert_max_spread( + belief_price, + max_spread, + offer_asset.amount, + return_amount, + spread_amount, + )?; + + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)? + .to_decimal256(LP_TOKEN_PRECISION)?; + + // Skip very small trade sizes which could significantly mess up the price due to rounding errors, + // especially if token precisions are 18. + if (swap_result.dy + swap_result.maker_fee + swap_result.share_fee) >= MIN_TRADE_SIZE + && offer_asset_dec.amount >= MIN_TRADE_SIZE + { + let last_price = swap_result.calc_last_price(offer_asset_dec.amount, offer_ind); + + // update_price() works only with internal representation + let ixs = [xs[0], xs[1] * config.pool_state.price_state.price_scale]; + config + .pool_state + .update_price(&config.pool_params, &env, total_share, &ixs, last_price)?; + } + + let receiver = to.unwrap_or_else(|| sender.clone()); + + let mut messages = vec![pools[ask_ind] + .info + .with_balance(return_amount) + .into_msg(&receiver)?]; + + // Send the shared fee + let mut fee_share_amount = Uint128::zero(); + if let Some(fee_share) = &config.fee_share { + fee_share_amount = swap_result.share_fee.to_uint(ask_asset_prec)?; + if !fee_share_amount.is_zero() { + let fee = pools[ask_ind].info.with_balance(fee_share_amount); + messages.push(fee.into_msg(&fee_share.recipient)?); + } + } + + // Send the maker fee + let mut maker_fee = Uint128::zero(); + if let Some(fee_address) = fee_info.fee_address { + maker_fee = swap_result.maker_fee.to_uint(ask_asset_prec)?; + if !maker_fee.is_zero() { + let fee = pools[ask_ind].info.with_balance(maker_fee); + messages.push(fee.into_msg(fee_address)?); + } + } + + accumulate_prices(&env, &mut config, old_real_price); + + // Reconcile orders + let cancel_msgs = ob_state.cancel_orders(&env.contract.address); + let order_msgs = ob_state.deploy_orders(&env, &config, &xs, &precisions)?; + + CONFIG.save(deps.storage, &config)?; + + let submsgs = ob_state.flatten_msgs_and_add_callback(&[messages, cancel_msgs, order_msgs]); + + Ok(response.add_submessages(submsgs).add_attributes([ + attr("action", "swap"), + attr("sender", sender), + attr("receiver", receiver), + attr("offer_asset", offer_asset_dec.info.to_string()), + attr("ask_asset", pools[ask_ind].info.to_string()), + attr("offer_amount", offer_asset.amount), + attr("return_amount", return_amount), + attr("spread_amount", spread_amount), + attr( + "commission_amount", + swap_result.total_fee.to_uint(ask_asset_prec)?, + ), + attr("maker_fee_amount", maker_fee), + attr("fee_share_amount", fee_share_amount), + ])) +} + +/// Updates the pool configuration with the specified parameters in the `params` variable. +/// +/// * **params** new parameter values in [`Binary`] form. +fn update_config( + deps: DepsMut, + env: Env, + info: MessageInfo, + params: Binary, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + let factory_config = query_factory_config(&deps.querier, &config.factory_addr)?; + + let owner = config.owner.as_ref().unwrap_or(&factory_config.owner); + ensure_eq!(&info.sender, owner, ContractError::Unauthorized {}); + + let mut attrs = vec![]; + + let action = match from_json(params)? { + ConcentratedPoolUpdateParams::Update(update_params) => { + config.pool_params.update_params(update_params)?; + "update_params" + } + ConcentratedPoolUpdateParams::Promote(promote_params) => { + config.pool_state.promote_params(&env, promote_params)?; + "promote_params" + } + ConcentratedPoolUpdateParams::StopChangingAmpGamma {} => { + config.pool_state.stop_promotion(&env); + "stop_changing_amp_gamma" + } + ConcentratedPoolUpdateParams::EnableFeeShare { + fee_share_bps, + fee_share_address, + } => { + // Enable fee sharing for this contract + // If fee sharing is already enabled, we should be able to overwrite + // the values currently set + + // Ensure the fee share isn't 0 and doesn't exceed the maximum allowed value + ensure!( + (0..MAX_FEE_SHARE_BPS).contains(&fee_share_bps), + ContractError::FeeShareOutOfBounds {} + ); + + // Set sharing config + config.fee_share = Some(FeeShareConfig { + bps: fee_share_bps, + recipient: deps.api.addr_validate(&fee_share_address)?, + }); + + CONFIG.save(deps.storage, &config)?; + + attrs.push(attr("fee_share_bps", fee_share_bps.to_string())); + attrs.push(attr("fee_share_address", fee_share_address)); + "enable_fee_share" + } + ConcentratedPoolUpdateParams::DisableFeeShare => { + // Disable fee sharing for this contract by setting bps and + // address back to None + config.fee_share = None; + CONFIG.save(deps.storage, &config)?; + "disable_fee_share" + } + }; + CONFIG.save(deps.storage, &config)?; + + Ok(Response::new() + .add_attribute("action", action) + .add_attributes(attrs)) +} + +pub fn process_custom_msgs( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: DualityPairMsg, +) -> Result { + match msg { + DualityPairMsg::SyncOrderbook {} => sync_pool_with_orderbook(deps, env, info), + DualityPairMsg::UpdateOrderbookConfig(update_orderbook_conf) => { + let config = CONFIG.load(deps.storage)?; + let factory_config = query_factory_config(&deps.querier, &config.factory_addr)?; + + let owner = config.owner.unwrap_or(factory_config.owner); + ensure_eq!(info.sender, owner, ContractError::Unauthorized {}); + + let mut ob_state = OrderbookState::load(deps.storage)?; + let cancel_orders_msgs = if let Some(false) = update_orderbook_conf.enable { + ob_state.orders = vec![]; + ob_state.cancel_orders(&env.contract.address) + } else { + vec![] + }; + + let mut attrs = vec![attr("action", "update_duality_orderbook_config")]; + attrs.extend(ob_state.update_config(deps.api, update_orderbook_conf)?); + + ob_state.save(deps.storage)?; + + Ok(Response::default() + .add_messages(cancel_orders_msgs) + .add_attributes(attrs)) + } + } +} diff --git a/contracts/pair_concentrated_duality/src/instantiate.rs b/contracts/pair_concentrated_duality/src/instantiate.rs new file mode 100644 index 000000000..5b3170a3d --- /dev/null +++ b/contracts/pair_concentrated_duality/src/instantiate.rs @@ -0,0 +1,143 @@ +use astroport::pair::ReplyIds; +use astroport::token_factory::tf_create_denom_msg; +use astroport::{ + asset::PairInfo, factory::PairType, pair::InstantiateMsg, pair_concentrated::UpdatePoolParams, + pair_concentrated_duality::ConcentratedDualityParams, +}; +use astroport_pcl_common::{ + state::{AmpGamma, Config, PoolParams, PoolState, Precisions, PriceState}, + utils::check_asset_infos, +}; +use cosmwasm_std::{ + ensure, from_json, Decimal256, DepsMut, Env, MessageInfo, Response, StdError, SubMsg, Uint128, +}; +use cw2::set_contract_version; + +use crate::error::ContractError; +use crate::orderbook::state::OrderbookState; +use crate::state::CONFIG; + +/// Contract name that is used for migration. +pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +/// Contract version that is used for migration. +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +/// Tokenfactory LP token subdenom +pub const LP_SUBDENOM: &str = "astroport/share"; +/// An LP token's precision. +pub const LP_TOKEN_PRECISION: u8 = 6; + +/// Creates a new contract with the specified parameters in the [`InstantiateMsg`]. +#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] +pub fn instantiate( + mut deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + if msg.asset_infos.len() != 2 { + return Err(StdError::generic_err("asset_infos must contain exactly two elements").into()); + } + + check_asset_infos(deps.api, &msg.asset_infos)?; + + // Duality orderbook supports only native assets + ensure!( + msg.asset_infos.iter().all(|x| x.is_native_token()), + ContractError::NonNativeAsset {} + ); + + let ConcentratedDualityParams { + main_params: params, + orderbook_config: ob_config, + } = from_json( + msg.init_params + .ok_or(ContractError::InitParamsNotFound {})?, + )?; + + if params.price_scale.is_zero() { + return Err(StdError::generic_err("Initial price scale can not be zero").into()); + } + + if params.track_asset_balances.unwrap_or_default() { + return Err(StdError::generic_err( + "Balance tracking is not available with Duality integration", + ) + .into()); + }; + + let factory_addr = deps.api.addr_validate(&msg.factory_addr)?; + + Precisions::store_precisions(deps.branch(), &msg.asset_infos, &factory_addr)?; + + // Initializing cumulative prices + let cumulative_prices = vec![ + ( + msg.asset_infos[0].clone(), + msg.asset_infos[1].clone(), + Uint128::zero(), + ), + ( + msg.asset_infos[1].clone(), + msg.asset_infos[0].clone(), + Uint128::zero(), + ), + ]; + + let ob_state = OrderbookState::new(deps.api, ob_config)?; + ob_state.save(deps.storage)?; + + let mut pool_params = PoolParams::default(); + pool_params.update_params(UpdatePoolParams { + mid_fee: Some(params.mid_fee), + out_fee: Some(params.out_fee), + fee_gamma: Some(params.fee_gamma), + repeg_profit_threshold: Some(params.repeg_profit_threshold), + min_price_scale_delta: Some(params.min_price_scale_delta), + ma_half_time: Some(params.ma_half_time), + })?; + + let pool_state = PoolState { + initial: AmpGamma::default(), + future: AmpGamma::new(params.amp, params.gamma)?, + future_time: env.block.time.seconds(), + initial_time: 0, + price_state: PriceState { + oracle_price: params.price_scale.into(), + last_price: params.price_scale.into(), + price_scale: params.price_scale.into(), + last_price_update: env.block.time.seconds(), + xcp_profit: Decimal256::zero(), + xcp_profit_real: Decimal256::zero(), + }, + }; + + let config = Config { + pair_info: PairInfo { + contract_addr: env.contract.address.clone(), + liquidity_token: "".to_owned(), + asset_infos: msg.asset_infos.clone(), + pair_type: PairType::Custom("concentrated_duality_orderbook".to_string()), + }, + factory_addr, + block_time_last: env.block.time.seconds(), + cumulative_prices, + pool_params, + pool_state, + owner: None, + track_asset_balances: false, + fee_share: None, + tracker_addr: None, + }; + + CONFIG.save(deps.storage, &config)?; + + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + // Create LP token + let sub_msg = SubMsg::reply_on_success( + tf_create_denom_msg(env.contract.address.to_string(), LP_SUBDENOM), + ReplyIds::CreateDenom as u64, + ); + + Ok(Response::new().add_submessage(sub_msg)) +} diff --git a/contracts/pair_concentrated_duality/src/lib.rs b/contracts/pair_concentrated_duality/src/lib.rs new file mode 100644 index 000000000..3cf3c63a0 --- /dev/null +++ b/contracts/pair_concentrated_duality/src/lib.rs @@ -0,0 +1,9 @@ +pub mod error; +pub mod execute; +pub mod instantiate; +pub mod migrate; +pub mod orderbook; +pub mod queries; +pub mod reply; +pub mod state; +pub mod utils; diff --git a/contracts/pair_concentrated_duality/src/migrate.rs b/contracts/pair_concentrated_duality/src/migrate.rs new file mode 100644 index 000000000..eef5c875a --- /dev/null +++ b/contracts/pair_concentrated_duality/src/migrate.rs @@ -0,0 +1,74 @@ +use cosmwasm_std::{entry_point, DepsMut, Empty, Env, Response, StdResult}; + +// const MIGRATE_FROM: &str = "astroport-pair-concentrated"; +// const MIGRATION_VERSION: &str = "4.0.1"; + +/// Manages the contract migration. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(_deps: DepsMut, _env: Env, _msg: Empty) -> StdResult { + todo!("add seamless migration from PCL to PCL with Duality integration") + + // let mut attrs = vec![]; + // + // let contract_info = CONTRACT.load(deps.storage)?; + // match msg { + // MigrateMsg::MigrateToOrderbook { params } => { + // if contract_info.contract != MIGRATE_FROM || contract_info.version != MIGRATION_VERSION + // { + // return Err(StdError::generic_err(format!( + // "Can't migrate from {} {}", + // contract_info.contract, contract_info.version + // ))); + // } + // + // let mut config: Config = Item::new("config").load(deps.storage)?; + // let base_precision = + // config.pair_info.asset_infos[0].decimals(&deps.querier, &config.factory_addr)?; + // let ob_state = OrderbookState::new( + // deps.querier, + // &env, + // ¶ms.market_id, + // params.orders_number, + // params.min_trades_to_avg, + // &config.pair_info.asset_infos, + // base_precision, + // )?; + // config.pair_info.pair_type = PairType::Custom("concentrated_inj_orderbook".to_string()); + // CONFIG.save(deps.storage, &config)?; + // ob_state.save(deps.storage)?; + // + // attrs.push(attr("action", "migrate_to_orderbook")); + // attrs.push(attr("subaccount_id", ob_state.subaccount.to_string())) + // } + // MigrateMsg::Migrate {} => { + // let contract_info = cw2::get_contract_version(deps.storage)?; + // match contract_info.contract.as_str() { + // CONTRACT_NAME => match contract_info.version.as_str() { + // "2.0.3" | "2.0.4" => {} + // _ => { + // return Err(StdError::generic_err(format!( + // "Can't migrate from {} {}", + // contract_info.contract, contract_info.version + // ))); + // } + // }, + // _ => { + // return Err(StdError::generic_err(format!( + // "Can't migrate from {} {}", + // contract_info.contract, contract_info.version + // ))); + // } + // } + // } + // } + // + // set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + // + // attrs.extend([ + // attr("previous_contract_name", contract_info.contract), + // attr("previous_contract_version", contract_info.version), + // attr("new_contract_name", CONTRACT_NAME), + // attr("new_contract_version", CONTRACT_VERSION), + // ]); + // Ok(Response::default().add_attributes(attrs)) +} diff --git a/contracts/pair_concentrated_duality/src/orderbook/consts.rs b/contracts/pair_concentrated_duality/src/orderbook/consts.rs new file mode 100644 index 000000000..3299622ea --- /dev/null +++ b/contracts/pair_concentrated_duality/src/orderbook/consts.rs @@ -0,0 +1,11 @@ +use std::ops::RangeInclusive; + +use cosmwasm_std::Decimal; + +/// Validation limits for order size. +pub const ORDER_SIZE_LIMITS: RangeInclusive = 1..=30; + +/// Min liquidity percent for orders to be placed in the orderbook. (0.01%) +pub const MIN_LIQUIDITY_PERCENT: Decimal = Decimal::raw(1e16 as u128); +/// Max liquidity percent for orders to be placed in the orderbook. (50%) +pub const MAX_LIQUIDITY_PERCENT: Decimal = Decimal::raw(5e17 as u128); diff --git a/contracts/pair_concentrated_duality/src/orderbook/error.rs b/contracts/pair_concentrated_duality/src/orderbook/error.rs new file mode 100644 index 000000000..d2adc7bf0 --- /dev/null +++ b/contracts/pair_concentrated_duality/src/orderbook/error.rs @@ -0,0 +1,20 @@ +use cosmwasm_std::{ConversionOverflowError, StdError}; +use thiserror::Error; + +use astroport_pcl_common::error::PclError; + +/// This enum describes pair contract errors +#[derive(Error, Debug, PartialEq)] +pub enum OrderbookError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + PclError(#[from] PclError), + + #[error("{0}")] + ConversionOverflowError(#[from] ConversionOverflowError), + + #[error("Orderbook is already synced")] + NoNeedToSync {}, +} diff --git a/contracts/pair_concentrated_duality/src/orderbook/execute.rs b/contracts/pair_concentrated_duality/src/orderbook/execute.rs new file mode 100644 index 000000000..17713b10b --- /dev/null +++ b/contracts/pair_concentrated_duality/src/orderbook/execute.rs @@ -0,0 +1,189 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + attr, ensure_eq, to_json_string, Decimal256, DepsMut, Env, MessageInfo, QuerierWrapper, + Response, +}; +use itertools::Itertools; + +use astroport::asset::{AssetInfoExt, DecimalAsset}; +use astroport::cosmwasm_ext::{DecimalToInteger, IntegerToDecimal}; +use astroport::pair::MIN_TRADE_SIZE; +use astroport::querier::{query_fee_info, query_native_supply, FeeInfo}; +use astroport_pcl_common::state::{Config, Precisions}; +use astroport_pcl_common::utils::accumulate_prices; + +use crate::error::ContractError; +use crate::instantiate::LP_TOKEN_PRECISION; +use crate::state::CONFIG; +use crate::utils::query_pools; + +use super::error::OrderbookError; +use super::state::OrderbookState; + +/// CumulativeTrade represents all trades that happened on orderbook as one trade. +/// I.e., swap from base_asset -> output_asset. +/// In this context, Astroport always charges protocol fees from quote asset. +#[cw_serde] +pub struct CumulativeTrade { + pub base_asset: DecimalAsset, + pub quote_asset: DecimalAsset, +} + +pub fn process_cumulative_trade( + querier: QuerierWrapper, + env: &Env, + trade: &CumulativeTrade, + config: &mut Config, + balances: &mut [&mut Decimal256], + precisions: &Precisions, + fee_info: Option<&FeeInfo>, +) -> Result { + let offer_ind = config + .pair_info + .asset_infos + .iter() + .position(|asset_info| asset_info == &trade.base_asset.info) + .unwrap(); + let ask_ind = 1 ^ offer_ind; + + let ixs = [ + *balances[0], + *balances[1] * config.pool_state.price_state.price_scale, + ]; + let fee_rate = config.pool_params.fee(&ixs); + let total_fee = fee_rate * trade.quote_asset.amount; + + let ask_asset_prec = precisions.get_precision(&config.pair_info.asset_infos[ask_ind])?; + let mut messages = vec![]; + let mut attrs = vec![]; + + let mut share_amount = Decimal256::zero(); + // Send the shared fee + if let Some(fee_share) = &config.fee_share { + share_amount = total_fee * Decimal256::from_ratio(fee_share.bps, 10000u16); + *balances[ask_ind] -= share_amount; + + let fee_share_amount = share_amount.to_uint(ask_asset_prec)?; + if !fee_share_amount.is_zero() { + let fee = config.pair_info.asset_infos[ask_ind].with_balance(fee_share_amount); + messages.push(fee.into_msg(&fee_share.recipient)?); + attrs.push(("fee_share_amount", fee_share_amount)) + } + } + + let fee_info = if let Some(fee_info) = fee_info { + fee_info + } else { + &query_fee_info( + &querier, + &config.factory_addr, + config.pair_info.pair_type.clone(), + )? + }; + // Send the maker fee + if let Some(fee_address) = &fee_info.fee_address { + let maker_share = (total_fee - share_amount) * Decimal256::from(fee_info.maker_fee_rate); + *balances[ask_ind] -= maker_share; + + let maker_fee = maker_share.to_uint(ask_asset_prec)?; + if !maker_fee.is_zero() { + let fee = config.pair_info.asset_infos[ask_ind].with_balance(maker_fee); + messages.push(fee.into_msg(fee_address)?); + attrs.push(("maker_fee_amount", maker_fee)); + } + } + + let old_real_price = config.pool_state.price_state.last_price; + + // Skip very small trade sizes which could significantly mess up the price due to rounding errors, + // especially if token precisions are 18. + if trade.base_asset.amount >= MIN_TRADE_SIZE && trade.quote_asset.amount >= MIN_TRADE_SIZE { + let last_price = if offer_ind == 0 { + trade.base_asset.amount / trade.quote_asset.amount + } else { + trade.quote_asset.amount / trade.base_asset.amount + }; + + let total_share = query_native_supply(&querier, &config.pair_info.liquidity_token)? + .to_decimal256(LP_TOKEN_PRECISION)?; + + let ixs = [ + *balances[0], + *balances[1] * config.pool_state.price_state.price_scale, + ]; + + config + .pool_state + .update_price(&config.pool_params, env, total_share, &ixs, last_price)?; + } + + // TODO: if process_cumulative_trade() originates from swap/provide/withdraw context, + // TODO: next TWAP entry won't be added in this block. + // TODO: Is it ok or should we post TWAP entry + // TODO: formed out of cumulative orderbook trade 1 second before current block time? + // Adding TWAP entry + accumulate_prices(env, config, old_real_price); + + Ok(Response::default() + .add_messages(messages) + .add_attributes(attrs)) +} + +pub fn sync_pool_with_orderbook( + deps: DepsMut, + env: Env, + info: MessageInfo, +) -> Result { + let mut ob_state = OrderbookState::load(deps.storage)?; + + if let Some(executor) = &ob_state.executor { + ensure_eq!(info.sender, executor, ContractError::Unauthorized {}); + } + + let precisions = Precisions::new(deps.storage)?; + + if let Some(cumulative_trade) = + ob_state.fetch_cumulative_trade(deps.as_ref(), &env.contract.address, &precisions)? + { + let mut config = CONFIG.load(deps.storage)?; + let mut pools = query_pools( + deps.as_ref(), + &env.contract.address, + &config, + &precisions, + &ob_state, + )?; + let mut balances = pools + .iter_mut() + .map(|asset| &mut asset.amount) + .collect_vec(); + + let response = process_cumulative_trade( + deps.querier, + &env, + &cumulative_trade, + &mut config, + &mut balances, + &precisions, + None, + )?; + + CONFIG.save(deps.storage, &config)?; + + let cancel_msgs = ob_state.cancel_orders(&env.contract.address); + + let balances = pools.iter().map(|asset| asset.amount).collect_vec(); + let order_msgs = ob_state.deploy_orders(&env, &config, &balances, &precisions)?; + + let submsgs = ob_state.flatten_msgs_and_add_callback(&[cancel_msgs, order_msgs]); + + Ok(response + .add_attributes([ + attr("action", "sync_pool_with_orderbook"), + attr("cumulative_trade", to_json_string(&cumulative_trade)?), + ]) + .add_submessages(submsgs)) + } else { + Err(OrderbookError::NoNeedToSync {}.into()) + } +} diff --git a/contracts/pair_concentrated_duality/src/orderbook/mod.rs b/contracts/pair_concentrated_duality/src/orderbook/mod.rs new file mode 100644 index 000000000..75c35c3e8 --- /dev/null +++ b/contracts/pair_concentrated_duality/src/orderbook/mod.rs @@ -0,0 +1,5 @@ +pub mod consts; +pub mod error; +pub mod execute; +pub mod state; +pub mod utils; diff --git a/contracts/pair_concentrated_duality/src/orderbook/state.rs b/contracts/pair_concentrated_duality/src/orderbook/state.rs new file mode 100644 index 000000000..1db4d3b5e --- /dev/null +++ b/contracts/pair_concentrated_duality/src/orderbook/state.rs @@ -0,0 +1,456 @@ +use std::cmp::Ordering; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + attr, coin, ensure, Addr, Api, Attribute, Coin, CosmosMsg, Decimal, Decimal256, Deps, Env, + ReplyOn, StdError, StdResult, Storage, SubMsg, Uint128, +}; +use cw_storage_plus::Item; +use itertools::Itertools; +use neutron_sdk::proto_types::cosmos::base::query::v1beta1::PageRequest; +use neutron_sdk::proto_types::neutron::dex::{ + DexQuerier, MsgCancelLimitOrder, MsgCancelLimitOrderResponse, MsgWithdrawFilledLimitOrder, +}; + +use astroport::asset::{Asset, AssetInfo, AssetInfoExt, Decimal256Ext, DecimalAsset}; +use astroport::cosmwasm_ext::IntegerToDecimal; +use astroport::pair_concentrated_duality::UpdateDualityOrderbook; +use astroport::pair_concentrated_duality::{OrderbookConfig, ReplyIds}; +use astroport_pcl_common::calc_d; +use astroport_pcl_common::state::{Config, Precisions}; + +use crate::error::ContractError; +use crate::orderbook::consts::{MAX_LIQUIDITY_PERCENT, MIN_LIQUIDITY_PERCENT, ORDER_SIZE_LIMITS}; +use crate::orderbook::error::OrderbookError; +use crate::orderbook::execute::CumulativeTrade; +use crate::orderbook::utils::{compute_swap, SpotOrdersFactory}; + +macro_rules! validate_param { + ($name:ident, $val:expr, $min:expr, $max:expr) => { + if !($min..$max).contains(&$val) { + return Err(StdError::generic_err(format!( + "Incorrect orderbook params: must be {min} <= {name} <= {max}, but value is {val}", + name = stringify!($name), + min = $min, + max = $max, + val = $val + ))); + } + }; +} + +#[cw_serde] +pub struct OrderbookState { + /// The address of the orderbook sync executor. If none the sync is permissionless. + pub executor: Option, + /// The number of trades on each side of the order book. + /// The higher this number is, the more gas the contract consumes. + pub orders_number: u8, + /// The minimum asset0 order size allowed in the order book. + pub min_asset_0_order_size: Uint128, + /// The minimum asset1 order size allowed in the order book. + pub min_asset_1_order_size: Uint128, + /// The percentage of the pool's liquidity that will be placed in the order book. + pub liquidity_percent: Decimal, + /// Array with tranche keys of all posted orders. + pub orders: Vec, + /// Last recorded balances on the orderbook. + pub last_balances: Vec, + /// Whether the orderbook integration enabled or not. + pub enabled: bool, +} + +const OB_CONFIG: Item = Item::new("orderbook_config"); + +impl OrderbookState { + pub fn new(api: &dyn Api, orderbook_config: OrderbookConfig) -> StdResult { + let config = Self { + orders_number: orderbook_config.orders_number, + min_asset_0_order_size: orderbook_config.min_asset_0_order_size, + min_asset_1_order_size: orderbook_config.min_asset_1_order_size, + liquidity_percent: orderbook_config.liquidity_percent, + orders: vec![], + last_balances: vec![], + enabled: orderbook_config.enable, + executor: orderbook_config.executor.map(Addr::unchecked), + }; + config.validate(api)?; + + Ok(config) + } + + #[inline] + pub fn load(storage: &dyn Storage) -> StdResult { + OB_CONFIG.load(storage) + } + + #[inline] + pub fn save(self, storage: &mut dyn Storage) -> StdResult<()> { + OB_CONFIG.save(storage, &self) + } + + pub fn update_config( + &mut self, + api: &dyn Api, + update_config: UpdateDualityOrderbook, + ) -> StdResult> { + let mut attrs = vec![]; + if let Some(enable) = update_config.enable { + attrs.push(attr("enabled", enable.to_string())); + self.enabled = enable; + } + + ensure!( + !update_config.remove_executor || update_config.executor.is_none(), + StdError::generic_err( + "Both 'remove_executor' and 'executor' cannot be set at the same time" + ) + ); + + if update_config.remove_executor { + attrs.push(attr("removed_executor", "true")); + self.executor = None; + } + + if let Some(executor) = update_config.executor { + attrs.push(attr("new_executor", &executor)); + self.executor = Some(Addr::unchecked(&executor)); + } + + if let Some(orders_number) = update_config.orders_number { + attrs.push(attr("orders_number", orders_number.to_string())); + self.orders_number = orders_number; + } + + if let Some(min_asset_0_order_size) = update_config.min_asset_0_order_size { + attrs.push(attr("min_asset_0_order_size", min_asset_0_order_size)); + self.min_asset_0_order_size = min_asset_0_order_size; + } + + if let Some(min_asset_1_order_size) = update_config.min_asset_1_order_size { + attrs.push(attr("min_asset_1_order_size", min_asset_1_order_size)); + self.min_asset_1_order_size = min_asset_1_order_size; + } + + if let Some(liquidity_percent) = update_config.liquidity_percent { + attrs.push(attr("liquidity_percent", liquidity_percent.to_string())); + self.liquidity_percent = liquidity_percent; + } + + self.validate(api)?; + + Ok(attrs) + } + + fn validate_orders_number(orders_number: u8) -> StdResult<()> { + validate_param!( + orders_number, + orders_number, + *ORDER_SIZE_LIMITS.start(), + *ORDER_SIZE_LIMITS.end() + ); + Ok(()) + } + + fn validate_liquidity_percent(liquidity_percent: Decimal) -> StdResult<()> { + validate_param!( + liquidity_percent, + liquidity_percent, + MIN_LIQUIDITY_PERCENT, + MAX_LIQUIDITY_PERCENT + ); + Ok(()) + } + + pub fn validate(&self, api: &dyn Api) -> StdResult<()> { + Self::validate_orders_number(self.orders_number)?; + Self::validate_liquidity_percent(self.liquidity_percent)?; + + ensure!( + !self.min_asset_0_order_size.is_zero(), + StdError::generic_err("min_asset_0_order_size must be greater than zero") + ); + ensure!( + !self.min_asset_1_order_size.is_zero(), + StdError::generic_err("min_asset_1_order_size must be greater than zero") + ); + + self.executor + .as_ref() + .map(|addr| api.addr_validate(addr.as_str())) + .transpose()?; + + Ok(()) + } + + /// Query orderbook liquidity. + /// If the force flag is false, this functions doesn't query orderbook if last balances are empty. + /// This hack helps us to avoid querying orderbook if integration is disabled. + pub fn query_ob_liquidity( + &self, + deps: Deps, + addr: &Addr, + force_update: bool, + ) -> StdResult> { + if !force_update && self.last_balances.is_empty() { + Ok(vec![]) + } else { + let dex_querier = DexQuerier::new(&deps.querier); + self.orders + .iter() + .map(|order_key| { + dex_querier + .simulate_cancel_limit_order(Some(MsgCancelLimitOrder { + creator: addr.to_string(), + tranche_key: order_key.to_owned(), + })) + .and_then(|res| match res.resp { + None + | Some(MsgCancelLimitOrderResponse { + taker_coin_out: None, + maker_coin_out: None, + }) => Err(StdError::generic_err("Unexpected duality response")), + Some(MsgCancelLimitOrderResponse { + taker_coin_out, + maker_coin_out, + }) => Ok([taker_coin_out, maker_coin_out] + .into_iter() + .filter_map(|coin| coin) + .collect_vec()), + }) + }) + .flatten_ok() + .collect::>>()? + .into_iter() + .into_group_map_by(|coin| coin.denom.clone()) + .into_iter() + .map(|(denom, coins)| { + let amounts: Vec = coins + .iter() + .map(|proto_coin| proto_coin.amount.parse()) + .try_collect()?; + let amount: Uint128 = amounts.iter().sum(); + Ok(coin(amount.u128(), denom)) + }) + .collect() + } + } + + /// Convert orderbook balances into DecimalAsset. + /// It is required that self.last_balances is updated before this method. + pub fn query_ob_liquidity_dec( + &self, + precisions: &Precisions, + ) -> Result, ContractError> { + self.last_balances + .iter() + .map(|coin| { + let asset = Asset::native(&coin.denom, coin.amount); + asset + .to_decimal_asset(precisions.get_precision(&asset.info)?) + .map_err(Into::into) + }) + .collect() + } + + /// Fetch all orders and save their tranche keys in the state. + pub fn fetch_all_orders(&mut self, deps: Deps, addr: &Addr) -> Result<(), OrderbookError> { + self.orders = DexQuerier::new(&deps.querier) + .limit_order_tranche_user_all_by_address( + addr.to_string(), + Some(PageRequest { + key: Default::default(), + offset: 0, + limit: (self.orders_number * 2) as u64, + count_total: false, + reverse: false, + }), + ) + .map(|res| { + res.limit_orders + .into_iter() + .map(|order| order.tranche_key) + .collect() + })?; + + Ok(()) + } + + /// Cancel orders and withdraw all balances from the orderbook. + pub fn cancel_orders(&self, addr: &Addr) -> Vec { + self.orders + .iter() + .flat_map(|tranche_key| { + let cancel_msg = MsgCancelLimitOrder { + creator: addr.to_string(), + tranche_key: tranche_key.clone(), + } + .into(); + let withdraw_msg = MsgWithdrawFilledLimitOrder { + creator: addr.to_string(), + tranche_key: tranche_key.clone(), + } + .into(); + + [cancel_msg, withdraw_msg] + }) + .collect() + } + + /// Fetch orderbook and check whether any of the orders have been executed. + /// Return CumulativeTrade object which is the difference between last and current balances. + /// Cache new balances in the state. + pub fn fetch_cumulative_trade( + &mut self, + deps: Deps, + addr: &Addr, + precisions: &Precisions, + ) -> Result, ContractError> { + let mut new_balances = self.query_ob_liquidity(deps, addr, false)?; + if !new_balances.is_empty() { + if self.last_balances[0] != new_balances[0] { + new_balances.swap(0, 1); + } + + let bal_diffs = self + .last_balances + .iter() + .zip(new_balances.iter()) + .map(|(a, b)| b.amount.abs_diff(a.amount)) + .collect_vec(); + + let diff_to_dec_asset = |ind: usize| -> Result<_, ContractError> { + let asset_info = AssetInfo::native(&new_balances[ind].denom); + let precision = precisions.get_precision(&asset_info)?; + Ok(asset_info.with_dec_balance(bal_diffs[ind].to_decimal256(precision)?)) + }; + + let maybe_trade = match self.last_balances[0].amount.cmp(&new_balances[0].amount) { + // We sold asset 0 for asset 1 + Ordering::Less => Some(CumulativeTrade { + base_asset: diff_to_dec_asset(1)?, + quote_asset: diff_to_dec_asset(0)?, + }), + // We bought asset 0 with asset 1 + Ordering::Greater => Some(CumulativeTrade { + base_asset: diff_to_dec_asset(0)?, + quote_asset: diff_to_dec_asset(1)?, + }), + // No trade happened + Ordering::Equal => None, + }; + + self.last_balances = new_balances; + + Ok(maybe_trade) + } else { + Ok(None) + } + } + + /// Construct an array with new orders. + /// Return an empty array if orderbook integration is disabled. + pub fn deploy_orders( + &self, + env: &Env, + config: &Config, + balances: &[Decimal256], + precisions: &Precisions, + ) -> Result, ContractError> { + // Orderbook is disabled. No need to deploy orders. + if !self.enabled { + return Ok(vec![]); + } + + let liquidity_percent_to_deploy = + Decimal256::from(self.liquidity_percent) / Decimal256::from_integer(2u128); + + let asset_0_liquidity = balances[0] * liquidity_percent_to_deploy; + let asset_1_liquidity = balances[1] * liquidity_percent_to_deploy; + + let asset_0_precision = precisions.get_precision(&config.pair_info.asset_infos[0])?; + let asset_1_precision = precisions.get_precision(&config.pair_info.asset_infos[1])?; + + let min_asset_0_order_size = self + .min_asset_0_order_size + .to_decimal256(asset_0_precision)?; + + let min_asset_1_order_size = self + .min_asset_1_order_size + .to_decimal256(asset_1_precision)?; + + let asset_0_trade_size = asset_0_liquidity / Decimal256::from_integer(self.orders_number); + let asset_1_trade_size = asset_1_liquidity / Decimal256::from_integer(self.orders_number); + + if asset_0_trade_size < min_asset_0_order_size + || asset_1_trade_size < min_asset_1_order_size + { + return Ok(vec![]); + } + + let amp_gamma = config.pool_state.get_amp_gamma(env); + let mut ixs = balances.to_vec(); + ixs[1] *= config.pool_state.price_state.price_scale; + let d = calc_d(&ixs, &_gamma)?; + + let mut orders_factory = SpotOrdersFactory::new( + &config.pair_info.asset_infos, + asset_0_precision, + asset_1_precision, + ); + + // Equal heights algorithm + for i in 1..=self.orders_number { + let i_dec = Decimal256::from_integer(i); + + let asset_1_sell_amount = asset_1_trade_size * i_dec; + let asset_0_sell_amount = + compute_swap(&ixs, asset_1_sell_amount, 0, config, amp_gamma, d)?; + + let sell_amount = asset_0_sell_amount / i_dec; + + let sell_price = if i > 1 { + (asset_1_sell_amount - orders_factory.orderbook_one_side_liquidity(false)) + / sell_amount + } else { + asset_1_sell_amount / sell_amount + }; + + let asset_0_buy_amount = asset_0_trade_size * i_dec; + let asset_1_buy_amount = + compute_swap(&ixs, asset_0_buy_amount, 1, config, amp_gamma, d)?; + + let buy_amount = asset_1_buy_amount / i_dec; + + let buy_price = if i > 1 { + (asset_0_buy_amount - orders_factory.orderbook_one_side_liquidity(true)) + / buy_amount + } else { + asset_0_buy_amount / buy_amount + }; + + // If at some point the price becomes zero, we don't post new orders + if sell_price.is_zero() || buy_price.is_zero() { + return Ok(vec![]); + } + + orders_factory.sell(sell_price, sell_amount); + orders_factory.buy(buy_price, buy_amount); + } + + Ok(orders_factory.collect_spot_orders(&env.contract.address)) + } + + /// Flatten all messages into one vector and add a callback to the last message only + /// if orderbook integration is enabled. + pub fn flatten_msgs_and_add_callback(&self, messages: &[Vec]) -> Vec { + let mut submsgs = messages.concat().into_iter().map(SubMsg::new).collect_vec(); + + if let (true, Some(last)) = (self.enabled, submsgs.last_mut()) { + last.id = ReplyIds::PostLimitOrderCb as u64; + last.reply_on = ReplyOn::Success; + } + + submsgs + } +} diff --git a/contracts/pair_concentrated_duality/src/orderbook/utils.rs b/contracts/pair_concentrated_duality/src/orderbook/utils.rs new file mode 100644 index 000000000..1f3e09ee5 --- /dev/null +++ b/contracts/pair_concentrated_duality/src/orderbook/utils.rs @@ -0,0 +1,207 @@ +use std::cmp::Ordering; + +use cosmwasm_std::{Addr, CosmosMsg, Decimal256, StdResult}; +use neutron_sdk::proto_types::neutron::dex::MsgPlaceLimitOrder; + +use astroport::asset::{AssetInfo, Decimal256Ext}; +use astroport_pcl_common::{ + calc_y, + state::{AmpGamma, Config}, +}; + +/// Calculate the swap result using cached D. +pub fn compute_swap( + ixs: &[Decimal256], + offer_amount: Decimal256, + ask_ind: usize, + config: &Config, + amp_gamma: AmpGamma, + d: Decimal256, +) -> StdResult { + let offer_ind = 1 ^ ask_ind; + + let offer_amount = if offer_ind == 1 { + offer_amount * config.pool_state.price_state.price_scale + } else { + offer_amount + }; + + let mut ixs = ixs.to_vec(); + ixs[offer_ind] += offer_amount; + + let new_y = calc_y(&ixs, d, &_gamma, ask_ind)?; + let mut dy = ixs[ask_ind] - new_y; + ixs[ask_ind] = new_y; + + if ask_ind == 1 { + dy /= config.pool_state.price_state.price_scale; + } + + let fee_rate = config.pool_params.fee(&ixs); + let total_fee = fee_rate * dy; + dy -= total_fee; + + Ok(dy) +} + +#[derive(Debug)] +struct AstroSpotOrder { + price: Decimal256, + amount: Decimal256, + is_buy: bool, +} + +/// Internal structure to handle spot orders +pub struct SpotOrdersFactory { + orders: Vec, + multiplier: [Decimal256; 2], + precision: [u8; 2], + denoms: Vec, +} + +impl SpotOrdersFactory { + pub fn new(asset_infos: &[AssetInfo], asset_0_precision: u8, asset_1_precision: u8) -> Self { + let denoms = asset_infos + .iter() + .map(|info| match &info { + AssetInfo::Token { .. } => unreachable!("cw20 token is not supported"), + AssetInfo::NativeToken { denom } => denom.clone(), + }) + .collect(); + + Self { + orders: vec![], + multiplier: [ + Decimal256::from_integer(10u64.pow(asset_0_precision as u32)), + Decimal256::from_integer(10u64.pow(asset_1_precision as u32)), + ], + precision: [asset_0_precision, asset_1_precision], + denoms, + } + } + + /// Buy asset_0 with asset_1 + /// (Sell asset_1 for asset_0) + pub fn buy(&mut self, price: Decimal256, amount: Decimal256) { + self.orders.push(AstroSpotOrder { + price, + amount, + is_buy: true, + }); + } + + /// Sell asset_0 for asset_1 + /// (Buy asset_1 with asset_0) + pub fn sell(&mut self, price: Decimal256, amount: Decimal256) { + self.orders.push(AstroSpotOrder { + price, + amount, + is_buy: false, + }); + } + + /// Calculate total sell/buy liquidity in one side + pub fn orderbook_one_side_liquidity(&self, is_buy: bool) -> Decimal256 { + self.orders + .iter() + .filter(|order| order.is_buy == is_buy) + .fold(Decimal256::zero(), |acc, order| { + acc + order.price * order.amount + }) + } + + pub fn collect_spot_orders(self, sender: &Addr) -> Vec { + self.orders + .into_iter() + .map(|order| { + if order.is_buy { + let limit_sell_price = + price_to_sci_notation(order.price, self.precision[1], self.precision[0]); + + #[allow(deprecated)] + MsgPlaceLimitOrder { + creator: sender.to_string(), + amount_in: (order.amount * self.multiplier[1]).floor().to_string(), + // order_type: LimitOrderType::GoodTilCancelled, + order_type: 0, // https://github.com/neutron-org/neutron/blob/main/proto/neutron/dex/tx.proto#L126 + max_amount_out: "".to_string(), + expiration_time: None, + receiver: sender.to_string(), + token_in: self.denoms[1].clone(), + token_out: self.denoms[0].clone(), + limit_sell_price, + tick_index_in_to_out: 0i64, + } + .into() + } else { + let limit_sell_price = + price_to_sci_notation(order.price, self.precision[0], self.precision[1]); + + #[allow(deprecated)] + MsgPlaceLimitOrder { + creator: sender.to_string(), + amount_in: (order.amount * self.multiplier[0]).floor().to_string(), + // order_type: LimitOrderType::GoodTilCancelled, + order_type: 0, // https://github.com/neutron-org/neutron/blob/main/proto/neutron/dex/tx.proto#L126 + max_amount_out: "".to_string(), + expiration_time: None, + receiver: sender.to_string(), + token_in: self.denoms[0].clone(), + token_out: self.denoms[1].clone(), + limit_sell_price, + tick_index_in_to_out: 0i64, + } + .into() + } + }) + .collect() + } +} + +/// Converting [`Decimal256`] price to float in scientific notation. +/// +/// For example, 1.0 ETH = 3000.0 USDC. ETH 18 decimals, USDC 6 decimals. +/// Sell ETH: 3000 / 1 * 10**(6-18) -> 3000e-12 uUSDC per aETH +/// Sell USDC: 1 / 3000 * 10**(18-6) -> 0.000333333333333333e12 -> 333333333.333333 aETH per uUSDC +fn price_to_sci_notation(price: Decimal256, base_precision: u8, quote_precision: u8) -> String { + let prec_diff = quote_precision as i8 - base_precision as i8; + match prec_diff.cmp(&0) { + Ordering::Less => format!("{price}E{prec_diff}"), + Ordering::Equal => price.to_string(), + Ordering::Greater => { + (price * Decimal256::from_integer(10u128.pow(prec_diff as u32))).to_string() + } + } +} + +#[cfg(test)] +mod unit_tests { + use super::*; + + #[test] + fn test_sci_notation_conversion() { + let price = Decimal256::from_ratio(1u8, 3000u64); + let base_precision = 6; + let quote_precision = 18; + assert_eq!( + price_to_sci_notation(price, base_precision, quote_precision), + "333333333.333333" + ); + + let price = Decimal256::from_ratio(3000u64, 1u8); + let base_precision = 18; + let quote_precision = 6; + assert_eq!( + price_to_sci_notation(price, base_precision, quote_precision), + "3000E-12" + ); + + let price = Decimal256::from_ratio(1u8, 2u8); + let base_precision = 6; + let quote_precision = 6; + assert_eq!( + price_to_sci_notation(price, base_precision, quote_precision), + "0.5" + ); + } +} diff --git a/contracts/pair_concentrated_duality/src/queries.rs b/contracts/pair_concentrated_duality/src/queries.rs new file mode 100644 index 000000000..d414cc6cc --- /dev/null +++ b/contracts/pair_concentrated_duality/src/queries.rs @@ -0,0 +1,364 @@ +use astroport::asset::Asset; +use astroport::cosmwasm_ext::{DecimalToInteger, IntegerToDecimal}; +use astroport::pair::{ + ConfigResponse, CumulativePricesResponse, PoolResponse, ReverseSimulationResponse, + SimulationResponse, +}; +use astroport::pair_concentrated::{ConcentratedPoolConfig, QueryMsg}; +use astroport::querier::{query_factory_config, query_fee_info, query_native_supply}; +use astroport_pcl_common::state::Precisions; +use astroport_pcl_common::utils::{ + accumulate_prices, before_swap_check, calc_last_prices, compute_offer_amount, compute_swap, + get_share_in_assets, +}; +use astroport_pcl_common::{calc_d, get_xcp}; +use cosmwasm_std::{ + to_json_binary, Binary, Decimal, Decimal256, DecimalRangeExceeded, Deps, Env, StdError, + StdResult, Uint128, +}; +use itertools::Itertools; + +use crate::error::ContractError; +use crate::instantiate::LP_TOKEN_PRECISION; +use crate::orderbook::state::OrderbookState; +use crate::state::CONFIG; +use crate::utils::{calculate_shares, get_assets_with_precision, pool_info, query_pools}; + +/// Exposes all the queries available in the contract. +/// +/// ## Queries +/// * **QueryMsg::Pair {}** Returns information about the pair in an object of type [`PairInfo`]. +/// +/// * **QueryMsg::Pool {}** Returns information about the amount of assets in the pair contract as +/// well as the amount of LP tokens issued using an object of type [`PoolResponse`]. +/// +/// * **QueryMsg::Share { amount }** Returns the amount of assets that could be withdrawn from the pool +/// using a specific amount of LP tokens. The result is returned in a vector that contains objects of type [`Asset`]. +/// +/// * **QueryMsg::Simulation { offer_asset }** Returns the result of a swap simulation using a [`SimulationResponse`] object. +/// +/// * **QueryMsg::ReverseSimulation { ask_asset }** Returns the result of a reverse swap simulation using +/// a [`ReverseSimulationResponse`] object. +/// +/// * **QueryMsg::CumulativePrices {}** Returns information about cumulative prices for the assets in the +/// pool using a [`CumulativePricesResponse`] object. +/// +/// * **QueryMsg::Config {}** Returns the configuration for the pair contract using a [`ConfigResponse`] object. +/// +/// * **QueryMsg::AssetBalanceAt { asset_info, block_height }** Returns the balance of the specified +/// asset that was in the pool just preceding the moment of the specified block height creation. +#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Pair {} => to_json_binary(&CONFIG.load(deps.storage)?.pair_info), + QueryMsg::Pool {} => to_json_binary(&query_pool(deps)?), + QueryMsg::Share { amount } => to_json_binary( + &query_share(deps, amount).map_err(|err| StdError::generic_err(err.to_string()))?, + ), + QueryMsg::Simulation { offer_asset, .. } => to_json_binary( + &query_simulation(deps, env, offer_asset) + .map_err(|err| StdError::generic_err(format!("{err}")))?, + ), + QueryMsg::ReverseSimulation { ask_asset, .. } => to_json_binary( + &query_reverse_simulation(deps, env, ask_asset) + .map_err(|err| StdError::generic_err(format!("{err}")))?, + ), + QueryMsg::CumulativePrices {} => to_json_binary( + &query_cumulative_prices(deps, env) + .map_err(|err| StdError::generic_err(format!("{err}")))?, + ), + QueryMsg::Observe { .. } => unimplemented!( + "Simple moving average observations has been removed from PCL for Duality" + ), + QueryMsg::Config {} => to_json_binary(&query_config(deps, env)?), + QueryMsg::LpPrice {} => to_json_binary(&query_lp_price(deps, env)?), + QueryMsg::ComputeD {} => to_json_binary(&query_compute_d(deps, env)?), + QueryMsg::AssetBalanceAt { .. } => { + unimplemented!("PCL for Duality doesn't support balances tracking") + } + QueryMsg::SimulateProvide { + assets, + slippage_tolerance, + } => to_json_binary( + &query_simulate_provide(deps, env, assets, slippage_tolerance) + .map_err(|err| StdError::generic_err(err.to_string()))?, + ), + QueryMsg::SimulateWithdraw { lp_amount } => to_json_binary( + &query_share(deps, lp_amount).map_err(|err| StdError::generic_err(err.to_string()))?, + ), + } +} + +/// Returns the amounts of assets in the pair contract as well as the amount of LP +/// tokens currently minted in an object of type [`PoolResponse`]. +fn query_pool(deps: Deps) -> StdResult { + let config = CONFIG.load(deps.storage)?; + pool_info(deps.querier, &config).map(|(assets, total_share)| PoolResponse { + assets, + total_share, + }) +} + +/// Returns the amount of assets that could be withdrawn from the pool using a specific amount of LP tokens. +/// The result is returned in a vector that contains objects of type [`Asset`]. +/// +/// * **amount** is the amount of LP tokens for which we calculate associated amounts of assets. +fn query_share(deps: Deps, amount: Uint128) -> StdResult> { + let config = CONFIG.load(deps.storage)?; + let precisions = Precisions::new(deps.storage)?; + let ob_state = OrderbookState::load(deps.storage)?; + + let pools = query_pools( + deps, + &config.pair_info.contract_addr, + &config, + &precisions, + &ob_state, + ) + .map_err(|err| StdError::generic_err(err.to_string()))?; + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)?; + let refund_assets = + get_share_in_assets(&pools, amount.saturating_sub(Uint128::one()), total_share); + + refund_assets + .into_iter() + .map(|asset| { + let prec = precisions.get_precision(&asset.info).unwrap(); + + Ok(Asset { + info: asset.info, + amount: asset.amount.to_uint(prec)?, + }) + }) + .collect() +} + +/// Returns information about a swap simulation. +pub fn query_simulation( + deps: Deps, + env: Env, + offer_asset: Asset, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let precisions = Precisions::new(deps.storage)?; + let offer_asset_prec = precisions.get_precision(&offer_asset.info)?; + let offer_asset_dec = offer_asset.to_decimal_asset(offer_asset_prec)?; + + let ob_state = OrderbookState::load(deps.storage)?; + + let pools = query_pools(deps, &env.contract.address, &config, &precisions, &ob_state)?; + + let (offer_ind, _) = pools + .iter() + .find_position(|asset| asset.info == offer_asset.info) + .ok_or_else(|| ContractError::InvalidAsset(offer_asset_dec.info.to_string()))?; + let ask_ind = 1 - offer_ind; + let ask_asset_prec = precisions.get_precision(&pools[ask_ind].info)?; + + before_swap_check(&pools, offer_asset_dec.amount)?; + + let xs = pools.iter().map(|asset| asset.amount).collect_vec(); + + // Get fee info from the factory + let fee_info = query_fee_info( + &deps.querier, + &config.factory_addr, + config.pair_info.pair_type.clone(), + )?; + let mut maker_fee_share = Decimal256::zero(); + if fee_info.fee_address.is_some() { + maker_fee_share = fee_info.maker_fee_rate.into(); + } + // If this pool is configured to share fees + let mut share_fee_share = Decimal256::zero(); + if let Some(fee_share) = config.fee_share.clone() { + share_fee_share = Decimal256::from_ratio(fee_share.bps, 10000u16); + } + + let swap_result = compute_swap( + &xs, + offer_asset_dec.amount, + ask_ind, + &config, + &env, + maker_fee_share, + share_fee_share, + )?; + + Ok(SimulationResponse { + return_amount: swap_result.dy.to_uint(ask_asset_prec)?, + spread_amount: swap_result.spread_fee.to_uint(ask_asset_prec)?, + commission_amount: swap_result.total_fee.to_uint(ask_asset_prec)?, + }) +} + +/// Returns information about a reverse swap simulation. +pub fn query_reverse_simulation( + deps: Deps, + env: Env, + ask_asset: Asset, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let precisions = Precisions::new(deps.storage)?; + let ask_asset_prec = precisions.get_precision(&ask_asset.info)?; + let ask_asset_dec = ask_asset.to_decimal_asset(ask_asset_prec)?; + + let ob_state = OrderbookState::load(deps.storage)?; + + let pools = query_pools(deps, &env.contract.address, &config, &precisions, &ob_state)?; + + let (ask_ind, _) = pools + .iter() + .find_position(|asset| asset.info == ask_asset.info) + .ok_or_else(|| ContractError::InvalidAsset(ask_asset.info.to_string()))?; + let offer_ind = 1 - ask_ind; + let offer_asset_prec = precisions.get_precision(&pools[offer_ind].info)?; + + let xs = pools.iter().map(|asset| asset.amount).collect_vec(); + let (offer_amount, spread_amount, commission_amount) = + compute_offer_amount(&xs, ask_asset_dec.amount, ask_ind, &config, &env)?; + + Ok(ReverseSimulationResponse { + offer_amount: offer_amount.to_uint(offer_asset_prec)?, + spread_amount: spread_amount.to_uint(offer_asset_prec)?, + commission_amount: commission_amount.to_uint(offer_asset_prec)?, + }) +} + +/// Returns information about cumulative prices for the assets in the pool. +fn query_cumulative_prices( + deps: Deps, + env: Env, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + let precisions = Precisions::new(deps.storage)?; + let ob_state = OrderbookState::load(deps.storage)?; + + let pools = query_pools(deps, &env.contract.address, &config, &precisions, &ob_state)?; + + let xs = pools.iter().map(|asset| asset.amount).collect_vec(); + let last_real_price = calc_last_prices(&xs, &config, &env)?; + + accumulate_prices(&env, &mut config, last_real_price); + + let (assets, total_share) = pool_info(deps.querier, &config)?; + + Ok(CumulativePricesResponse { + assets, + total_share, + cumulative_prices: config.cumulative_prices, + }) +} + +/// Compute the current LP token virtual price. +pub fn query_lp_price(deps: Deps, env: Env) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let total_lp = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)? + .to_decimal256(LP_TOKEN_PRECISION)?; + let ob_state = OrderbookState::load(deps.storage)?; + + if !total_lp.is_zero() { + let precisions = Precisions::new(deps.storage)?; + let mut ixs = query_pools(deps, &env.contract.address, &config, &precisions, &ob_state) + .map_err(|err| StdError::generic_err(err.to_string()))? + .into_iter() + .map(|asset| asset.amount) + .collect_vec(); + ixs[1] *= config.pool_state.price_state.price_scale; + let amp_gamma = config.pool_state.get_amp_gamma(&env); + let d = calc_d(&ixs, &_gamma)?; + let xcp = get_xcp(d, config.pool_state.price_state.price_scale); + + Ok(xcp / total_lp) + } else { + Ok(Decimal256::zero()) + } +} + +/// Returns the pair contract configuration. +pub fn query_config(deps: Deps, env: Env) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let amp_gamma = config.pool_state.get_amp_gamma(&env); + let price_scale = config + .pool_state + .price_state + .price_scale + .try_into() + .map_err(|err: DecimalRangeExceeded| StdError::generic_err(err.to_string()))?; + + let factory_config = query_factory_config(&deps.querier, &config.factory_addr)?; + + Ok(ConfigResponse { + block_time_last: config.block_time_last, + params: Some(to_json_binary(&ConcentratedPoolConfig { + amp: amp_gamma.amp, + gamma: amp_gamma.gamma, + mid_fee: config.pool_params.mid_fee, + out_fee: config.pool_params.out_fee, + fee_gamma: config.pool_params.fee_gamma, + repeg_profit_threshold: config.pool_params.repeg_profit_threshold, + min_price_scale_delta: config.pool_params.min_price_scale_delta, + price_scale, + ma_half_time: config.pool_params.ma_half_time, + track_asset_balances: config.track_asset_balances, + fee_share: config.fee_share, + })?), + owner: config.owner.unwrap_or(factory_config.owner), + factory_addr: config.factory_addr, + tracker_addr: config.tracker_addr, + }) +} + +/// Compute the current pool D value. +pub fn query_compute_d(deps: Deps, env: Env) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let precisions = Precisions::new(deps.storage)?; + + let ob_state = OrderbookState::load(deps.storage)?; + + let mut xs = query_pools(deps, &env.contract.address, &config, &precisions, &ob_state) + .map_err(|e| StdError::generic_err(e.to_string()))? + .into_iter() + .map(|a| a.amount) + .collect_vec(); + + if xs[0].is_zero() || xs[1].is_zero() { + return Err(StdError::generic_err("Pools are empty")); + } + + xs[1] *= config.pool_state.price_state.price_scale; + + let amp_gamma = config.pool_state.get_amp_gamma(&env); + calc_d(&xs, &_gamma) +} + +pub fn query_simulate_provide( + deps: Deps, + env: Env, + mut assets: Vec, + slippage_tolerance: Option, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)? + .to_decimal256(LP_TOKEN_PRECISION)?; + + let precisions = Precisions::new(deps.storage)?; + + let ob_state = OrderbookState::load(deps.storage)?; + + let pools = query_pools(deps, &env.contract.address, &config, &precisions, &ob_state)?; + + let deposits = get_assets_with_precision(deps, &config, &mut assets, &pools, &precisions)?; + + let (share_uint128, _) = calculate_shares( + &env, + &mut config, + &pools, + total_share, + &deposits, + slippage_tolerance, + )?; + + Ok(share_uint128) +} diff --git a/contracts/pair_concentrated_duality/src/reply.rs b/contracts/pair_concentrated_duality/src/reply.rs new file mode 100644 index 000000000..7cb2cdcd6 --- /dev/null +++ b/contracts/pair_concentrated_duality/src/reply.rs @@ -0,0 +1,49 @@ +use cosmwasm_std::{DepsMut, Env, Reply, Response, StdError, SubMsgResponse, SubMsgResult}; + +use astroport::pair_concentrated_duality::ReplyIds; +use astroport::token_factory::MsgCreateDenomResponse; + +use crate::error::ContractError; +use crate::orderbook::state::OrderbookState; +use crate::state::CONFIG; + +/// The entry point to the contract for processing replies from submessages. +#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] +pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { + match ReplyIds::try_from(msg.id)? { + ReplyIds::CreateDenom => { + if let SubMsgResult::Ok(SubMsgResponse { data: Some(b), .. }) = msg.result { + let MsgCreateDenomResponse { new_token_denom } = b.try_into()?; + + CONFIG.update(deps.storage, |mut config| { + if !config.pair_info.liquidity_token.is_empty() { + return Err(StdError::generic_err( + "Liquidity token is already set in the config", + )); + } + + config + .pair_info + .liquidity_token + .clone_from(&new_token_denom); + + Ok(config) + })?; + + Ok(Response::new().add_attribute("lp_denom", new_token_denom)) + } else { + Err(ContractError::FailedToParseReply {}) + } + } + ReplyIds::PostLimitOrderCb => { + // Query total liquidity sitting on orderbook and cache it in the contract state + let mut ob_state = OrderbookState::load(deps.storage)?; + ob_state.fetch_all_orders(deps.as_ref(), &env.contract.address)?; + ob_state.last_balances = + ob_state.query_ob_liquidity(deps.as_ref(), &env.contract.address, true)?; + ob_state.save(deps.storage)?; + + Ok(Response::default().add_attribute("action", "post_limit_order_callback")) + } + } +} diff --git a/contracts/pair_concentrated_duality/src/state.rs b/contracts/pair_concentrated_duality/src/state.rs new file mode 100644 index 000000000..eaa8f30d3 --- /dev/null +++ b/contracts/pair_concentrated_duality/src/state.rs @@ -0,0 +1,10 @@ +use cw_storage_plus::Item; + +use astroport::common::OwnershipProposal; +use astroport_pcl_common::state::Config; + +/// Stores pool parameters and state. +pub const CONFIG: Item = Item::new("config"); + +/// Stores the latest contract ownership transfer proposal +pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); diff --git a/contracts/pair_concentrated_duality/src/utils.rs b/contracts/pair_concentrated_duality/src/utils.rs new file mode 100644 index 000000000..bab4c7eeb --- /dev/null +++ b/contracts/pair_concentrated_duality/src/utils.rs @@ -0,0 +1,268 @@ +use cosmwasm_std::{ + ensure, Addr, Decimal, Decimal256, Deps, Env, QuerierWrapper, StdError, StdResult, Uint128, +}; +use itertools::Itertools; + +use astroport::asset::{ + Asset, AssetInfoExt, Decimal256Ext, DecimalAsset, MINIMUM_LIQUIDITY_AMOUNT, +}; +use astroport::cosmwasm_ext::{AbsDiff, DecimalToInteger, IntegerToDecimal}; +use astroport::pair::MIN_TRADE_SIZE; +use astroport::querier::query_native_supply; +use astroport_pcl_common::state::{Config, Precisions}; +use astroport_pcl_common::utils::{ + assert_slippage_tolerance, calc_provide_fee, check_assets, check_pair_registered, +}; +use astroport_pcl_common::{calc_d, get_xcp}; + +use crate::error::ContractError; +use crate::instantiate::LP_TOKEN_PRECISION; +use crate::orderbook::state::OrderbookState; + +pub(crate) fn query_contract_balances( + querier: QuerierWrapper, + addr: &Addr, + config: &Config, + precisions: &Precisions, +) -> Result, ContractError> { + config + .pair_info + .query_pools(&querier, addr)? + .into_iter() + .map(|asset| { + asset + .to_decimal_asset(precisions.get_precision(&asset.info)?) + .map_err(Into::into) + }) + .collect() +} + +/// Returns current pool's volumes where amount is in [`Decimal256`] form. +/// Query contract balances and orderbook liquidity and merge them. +pub(crate) fn query_pools( + deps: Deps, + addr: &Addr, + config: &Config, + precisions: &Precisions, + ob_state: &OrderbookState, +) -> Result, ContractError> { + let contract_liq = query_contract_balances(deps.querier, addr, config, precisions)?; + let ob_liquidity = ob_state.query_ob_liquidity_dec(precisions)?; + + let mut balances = contract_liq + .iter() + .chain(ob_liquidity.iter()) + .into_group_map_by(|asset| asset.info.clone()) + .into_iter() + .map(|(info, assets)| { + let sum = assets + .iter() + .fold(Decimal256::zero(), |acc, a| acc + a.amount); + Ok(info.with_dec_balance(sum)) + }) + .collect::>>()?; + + if balances[0].info != config.pair_info.asset_infos[0] { + balances.swap(0, 1); + } + + Ok(balances) +} + +/// Returns the total amount of assets in the pool as well as the total amount of LP tokens currently minted. +pub(crate) fn pool_info( + querier: QuerierWrapper, + config: &Config, +) -> StdResult<(Vec, Uint128)> { + let pools = config + .pair_info + .query_pools(&querier, &config.pair_info.contract_addr)?; + let total_share = query_native_supply(&querier, &config.pair_info.liquidity_token)?; + + Ok((pools, total_share)) +} + +pub(crate) fn get_assets_with_precision( + deps: Deps, + config: &Config, + assets: &mut Vec, + pools: &[DecimalAsset], + precisions: &Precisions, +) -> Result, ContractError> { + if !check_pair_registered( + deps.querier, + &config.factory_addr, + &config.pair_info.asset_infos, + )? { + return Err(ContractError::PairIsNotRegistered {}); + } + + match assets.len() { + 0 => { + return Err(StdError::generic_err("Nothing to provide").into()); + } + 1 => { + // Append omitted asset with explicit zero amount + let (given_ind, _) = config + .pair_info + .asset_infos + .iter() + .find_position(|pool| pool.equal(&assets[0].info)) + .ok_or_else(|| ContractError::InvalidAsset(assets[0].info.to_string()))?; + assets.push(Asset { + info: config.pair_info.asset_infos[1 ^ given_ind].clone(), + amount: Uint128::zero(), + }); + } + 2 => {} + _ => { + return Err(ContractError::InvalidNumberOfAssets( + config.pair_info.asset_infos.len(), + )) + } + } + + check_assets(deps.api, assets)?; + + if pools[0].info.equal(&assets[1].info) { + assets.swap(0, 1); + } + + // precisions.get_precision() also validates that the asset belongs to the pool + Ok(vec![ + Decimal256::with_precision(assets[0].amount, precisions.get_precision(&assets[0].info)?)?, + Decimal256::with_precision(assets[1].amount, precisions.get_precision(&assets[1].info)?)?, + ]) +} + +pub(crate) fn calculate_shares( + env: &Env, + config: &mut Config, + pools: &[DecimalAsset], + total_share: Decimal256, + deposits: &[Decimal256], + slippage_tolerance: Option, +) -> Result<(Uint128, Decimal256), ContractError> { + // Initial provide cannot be one-sided + if total_share.is_zero() && (deposits[0].is_zero() || deposits[1].is_zero()) { + return Err(ContractError::InvalidZeroAmount {}); + } + + let mut new_xp = pools + .iter() + .enumerate() + .map(|(ind, pool)| pool.amount + deposits[ind]) + .collect_vec(); + new_xp[1] *= config.pool_state.price_state.price_scale; + + let amp_gamma = config.pool_state.get_amp_gamma(env); + let new_d = calc_d(&new_xp, &_gamma)?; + + let share = if total_share.is_zero() { + let xcp = get_xcp(new_d, config.pool_state.price_state.price_scale); + let mint_amount = xcp + .checked_sub(MINIMUM_LIQUIDITY_AMOUNT.to_decimal256(LP_TOKEN_PRECISION)?) + .map_err(|_| ContractError::MinimumLiquidityAmountError {})?; + + // share cannot become zero after minimum liquidity subtraction + if mint_amount.is_zero() { + return Err(ContractError::MinimumLiquidityAmountError {}); + } + + config.pool_state.price_state.xcp_profit_real = Decimal256::one(); + config.pool_state.price_state.xcp_profit = Decimal256::one(); + + mint_amount + } else { + let mut old_xp = pools.iter().map(|a| a.amount).collect_vec(); + old_xp[1] *= config.pool_state.price_state.price_scale; + let old_d = calc_d(&old_xp, &_gamma)?; + let share = (total_share * new_d / old_d).saturating_sub(total_share); + + let mut ideposits = deposits.to_vec(); + ideposits[1] *= config.pool_state.price_state.price_scale; + + share * (Decimal256::one() - calc_provide_fee(&ideposits, &new_xp, &config.pool_params)) + }; + + // calculate accrued share + let share_ratio = share / (total_share + share); + let balanced_share = [ + new_xp[0] * share_ratio, + new_xp[1] * share_ratio / config.pool_state.price_state.price_scale, + ]; + let assets_diff = [ + deposits[0].diff(balanced_share[0]), + deposits[1].diff(balanced_share[1]), + ]; + + let mut slippage = Decimal256::zero(); + + // If deposit doesn't diverge too much from the balanced share, we don't update the price + if assets_diff[0] >= MIN_TRADE_SIZE && assets_diff[1] >= MIN_TRADE_SIZE { + slippage = assert_slippage_tolerance( + deposits, + share, + &config.pool_state.price_state, + slippage_tolerance, + )?; + + let last_price = assets_diff[0] / assets_diff[1]; + config.pool_state.update_price( + &config.pool_params, + env, + total_share + share, + &new_xp, + last_price, + )?; + } + + Ok((share.to_uint(LP_TOKEN_PRECISION)?, slippage)) +} + +pub fn ensure_min_assets_to_receive( + config: &Config, + refund_assets: &[Asset], + min_assets_to_receive: Option>, +) -> Result<(), ContractError> { + if let Some(mut min_assets_to_receive) = min_assets_to_receive { + ensure!( + min_assets_to_receive.len() == min_assets_to_receive.len(), + ContractError::WrongAssetLength { + expected: refund_assets.len(), + actual: min_assets_to_receive.len(), + } + ); + + for asset in &min_assets_to_receive { + ensure!( + config.pair_info.asset_infos.contains(&asset.info), + ContractError::InvalidAsset(asset.info.to_string()) + ); + } + + if refund_assets[0].info.ne(&min_assets_to_receive[0].info) { + min_assets_to_receive.swap(0, 1) + } + + ensure!( + refund_assets[0].amount >= min_assets_to_receive[0].amount, + ContractError::WithdrawSlippageViolation { + asset_name: refund_assets[0].info.to_string(), + received: refund_assets[0].amount, + expected: min_assets_to_receive[0].amount, + } + ); + + ensure!( + refund_assets[1].amount >= min_assets_to_receive[1].amount, + ContractError::WithdrawSlippageViolation { + asset_name: refund_assets[1].info.to_string(), + received: refund_assets[1].amount, + expected: min_assets_to_receive[1].amount, + } + ); + } + + Ok(()) +} diff --git a/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs b/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs new file mode 100644 index 000000000..dbe894ed9 --- /dev/null +++ b/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs @@ -0,0 +1,268 @@ +#![cfg(not(tarpaulin_include))] +#![cfg(feature = "test-tube")] +#![allow(dead_code)] + +use std::collections::HashMap; + +use anyhow::Result as AnyResult; +use cosmwasm_std::{coin, coins, to_json_binary, Addr, Coin, Decimal}; +use itertools::Itertools; +use neutron_test_tube::{Account, ExecuteResponse, SigningAccount}; + +use astroport::pair_concentrated_duality::DualityPairMsg; +use astroport::{ + asset::{native_asset_info, Asset, AssetInfo, PairInfo}, + factory::{PairConfig, PairType}, + pair_concentrated::ConcentratedPoolParams, + pair_concentrated_duality::{ConcentratedDualityParams, OrderbookConfig}, +}; +use astroport_test::coins::TestCoin; +use astroport_test::convert::f64_to_dec; + +use super::neutron_wrapper::TestAppWrapper; + +type ExecuteMsg = astroport::pair::ExecuteMsgExt; + +const INIT_BALANCE: u128 = u128::MAX; + +pub fn init_native_coins(test_coins: &[TestCoin]) -> Vec { + let mut has_ntrn = false; + let mut test_coins: Vec = test_coins + .iter() + .filter_map(|test_coin| match test_coin { + TestCoin::Native(name) => { + if name == "untrn" { + has_ntrn = true; + }; + Some(coin(INIT_BALANCE, name)) + } + _ => None, + }) + .collect(); + if !has_ntrn { + test_coins.push(coin(INIT_BALANCE, "untrn")); + } + + test_coins +} + +pub struct AstroportHelper<'a> { + pub helper: TestAppWrapper<'a>, + pub owner: SigningAccount, + pub assets: HashMap, + pub factory: Addr, + pub maker: Addr, + pub pair_addr: Addr, + pub lp_token: String, + pub token_a: String, + pub token_b: String, +} + +impl<'a> AstroportHelper<'a> { + pub fn new( + helper: TestAppWrapper<'a>, + test_coins: Vec, + params: ConcentratedPoolParams, + orderbook_config: OrderbookConfig, + ) -> AnyResult { + let signer = helper.app.init_account(&init_native_coins(&test_coins))?; + let owner = Addr::unchecked(signer.address()); + + let asset_infos_vec: Vec<_> = test_coins + .clone() + .into_iter() + .filter_map(|coin| Some((coin.clone(), native_asset_info(coin.denom()?)))) + .collect(); + + // We don't support cw20 + // test_coins.iter().for_each(|coin| { + // if let Some((name, decimals)) = coin.cw20_init_data() { + // let token_addr = Self::init_token(&helper, name, decimals, &owner); + // asset_infos_vec.push((coin.clone(), token_asset_info(token_addr))) + // } + // }); + + let maker = helper.app.init_account(&[])?; + let maker_addr = Addr::unchecked(maker.address()); + + let coin_registry_address = helper + .init_contract( + helper.code_ids["coin-registry"], + &astroport::native_coin_registry::InstantiateMsg { + owner: owner.to_string(), + }, + &[], + ) + .unwrap(); + + asset_infos_vec + .iter() + .try_for_each(|(test_coin, _)| match &test_coin { + TestCoin::NativePrecise(denom, decimals) => helper + .execute_contract( + &signer, + coin_registry_address.as_str(), + &astroport::native_coin_registry::ExecuteMsg::Add { + native_coins: vec![(denom.to_owned(), *decimals)], + }, + &[], + ) + .map(|_| ()), + TestCoin::Native(denom) => helper + .execute_contract( + &signer, + coin_registry_address.as_str(), + &astroport::native_coin_registry::ExecuteMsg::Add { + native_coins: vec![(denom.to_owned(), 6)], + }, + &[], + ) + .map(|_| ()), + _ => Ok(()), + }) + .unwrap(); + + let pair_type = PairType::Custom("concentrated_duality_orderbook".to_string()); + + let init_msg = astroport::factory::InstantiateMsg { + fee_address: Some(maker_addr.to_string()), + pair_configs: vec![PairConfig { + code_id: helper.code_ids["pair-concentrated-duality"], + maker_fee_bps: 5000, + total_fee_bps: 0u16, // Concentrated pair does not use this field, + pair_type: pair_type.clone(), + is_disabled: false, + is_generator_disabled: false, + permissioned: true, + }], + token_code_id: 0, + generator_address: None, + owner: owner.to_string(), + whitelist_code_id: 0, + coin_registry_address: coin_registry_address.to_string(), + tracker_config: None, + }; + + let factory = helper + .init_contract(helper.code_ids["factory"], &init_msg, &[]) + .unwrap(); + + let asset_infos = asset_infos_vec + .clone() + .into_iter() + .map(|(_, asset_info)| asset_info) + .collect_vec(); + + let pcl_duality_params = ConcentratedDualityParams { + main_params: params, + orderbook_config, + }; + + let init_pair_msg = astroport::factory::ExecuteMsg::CreatePair { + pair_type, + asset_infos: asset_infos.clone(), + init_params: Some(to_json_binary(&pcl_duality_params).unwrap()), + }; + + helper + .execute_contract(&signer, factory.as_str(), &init_pair_msg, &[]) + .unwrap(); + + let PairInfo { + liquidity_token, + contract_addr, + .. + } = helper.smart_query( + &factory, + &astroport::factory::QueryMsg::Pair { + asset_infos: asset_infos.clone(), + }, + )?; + + Ok(Self { + helper, + owner: signer, + assets: HashMap::new(), + maker: maker_addr, + factory: Addr::unchecked(factory), + pair_addr: contract_addr, + lp_token: liquidity_token, + token_a: asset_infos[0].to_string(), + token_b: asset_infos[1].to_string(), + }) + } + + pub fn provide_liquidity(&self, sender: &SigningAccount, assets: &[Asset]) -> AnyResult<()> { + self.provide_liquidity_with_slip_tolerance( + sender, + assets, + Some(f64_to_dec(0.5)), // 50% slip tolerance for testing purposes + ) + } + + pub fn provide_liquidity_with_slip_tolerance( + &self, + sender: &SigningAccount, + assets: &[Asset], + slippage_tolerance: Option, + ) -> AnyResult<()> { + let funds = assets.iter().map(|a| a.as_coin().unwrap()).collect_vec(); + + let msg = ExecuteMsg::ProvideLiquidity { + assets: assets.to_vec(), + slippage_tolerance, + auto_stake: None, + receiver: None, + min_lp_to_receive: None, + }; + + self.helper + .execute_contract(sender, self.pair_addr.as_str(), &msg, &funds) + .map(|_| ()) + } + + pub fn withdraw_liquidity(&mut self, sender: &SigningAccount, amount: u128) -> AnyResult<()> { + let msg = ExecuteMsg::WithdrawLiquidity { + assets: vec![], + min_assets_to_receive: None, + }; + + self.helper + .execute_contract( + sender, + self.pair_addr.as_str(), + &msg, + &coins(amount, &self.lp_token), + ) + .map(|_| ()) + } + + pub fn swap( + &self, + sender: &SigningAccount, + offer_asset: &Asset, + max_spread: Option, + ) -> AnyResult< + ExecuteResponse< + neutron_test_tube::cosmrs::proto::cosmwasm::wasm::v1::MsgExecuteContractResponse, + >, + > { + match &offer_asset.info { + AssetInfo::Token { .. } => unimplemented!(), + AssetInfo::NativeToken { .. } => { + let funds = [offer_asset.as_coin().unwrap()]; + + let msg = ExecuteMsg::Swap { + offer_asset: offer_asset.clone(), + ask_asset_info: None, + belief_price: None, + max_spread, + to: None, + }; + + self.helper + .execute_contract(sender, self.pair_addr.as_str(), &msg, &funds) + } + } + } +} diff --git a/contracts/pair_concentrated_duality/tests/common/helper.rs b/contracts/pair_concentrated_duality/tests/common/helper.rs new file mode 100644 index 000000000..b22d141b0 --- /dev/null +++ b/contracts/pair_concentrated_duality/tests/common/helper.rs @@ -0,0 +1,527 @@ +#![cfg(not(tarpaulin_include))] +#![allow(dead_code)] + +use std::collections::HashMap; + +use anyhow::Result as AnyResult; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::testing::MockApi; +use cosmwasm_std::{ + coin, coins, from_json, to_json_binary, Addr, BankMsg, Decimal, Decimal256, Empty, GovMsg, + IbcMsg, IbcQuery, MemoryStorage, StdError, StdResult, Uint128, +}; +use derivative::Derivative; +use itertools::Itertools; + +use astroport::asset::{native_asset_info, Asset, AssetInfo, PairInfo}; +use astroport::factory::{PairConfig, PairType}; +use astroport::observation::OracleObservation; +use astroport::pair::{ + ConfigResponse, CumulativePricesResponse, ExecuteMsgExt, PoolResponse, + ReverseSimulationResponse, SimulationResponse, +}; +use astroport::pair_concentrated::{ + ConcentratedPoolConfig, ConcentratedPoolParams, ConcentratedPoolUpdateParams, QueryMsg, +}; +use astroport::pair_concentrated_duality::{ + ConcentratedDualityParams, DualityPairMsg, OrderbookConfig, +}; +use astroport_pair_concentrated_duality::orderbook::state::OrderbookState; +use astroport_pcl_common::state::Config; +use astroport_test::coins::TestCoin; +use astroport_test::convert::f64_to_dec; +use astroport_test::cw_multi_test::{ + no_init, App, AppResponse, BankKeeper, BankSudo, BasicAppBuilder, Contract, ContractWrapper, + DistributionKeeper, Executor, FailingModule, StakeKeeper, WasmKeeper, +}; +use astroport_test::modules::neutron_stargate::NeutronStargate; + +pub type ExecuteMsg = ExecuteMsgExt; + +pub fn common_pcl_params() -> ConcentratedPoolParams { + ConcentratedPoolParams { + amp: f64_to_dec(40f64), + gamma: f64_to_dec(0.000145), + mid_fee: f64_to_dec(0.0026), + out_fee: f64_to_dec(0.0045), + fee_gamma: f64_to_dec(0.00023), + repeg_profit_threshold: f64_to_dec(0.000002), + min_price_scale_delta: f64_to_dec(0.000146), + price_scale: Decimal::one(), + ma_half_time: 600, + track_asset_balances: None, + fee_share: None, + } +} + +#[cw_serde] +pub struct AmpGammaResponse { + pub amp: Decimal, + pub gamma: Decimal, + pub future_time: u64, +} + +fn pcl_duality_contract() -> Box> { + Box::new( + ContractWrapper::new( + astroport_pair_concentrated_duality::execute::execute, + astroport_pair_concentrated_duality::instantiate::instantiate, + astroport_pair_concentrated_duality::queries::query, + ) + .with_reply_empty(astroport_pair_concentrated_duality::reply::reply), + ) +} + +fn coin_registry_contract() -> Box> { + Box::new(ContractWrapper::new_with_empty( + astroport_native_coin_registry::contract::execute, + astroport_native_coin_registry::contract::instantiate, + astroport_native_coin_registry::contract::query, + )) +} + +fn factory_contract() -> Box> { + Box::new( + ContractWrapper::new_with_empty( + astroport_factory::contract::execute, + astroport_factory::contract::instantiate, + astroport_factory::contract::query, + ) + .with_reply_empty(astroport_factory::contract::reply), + ) +} + +pub type NeutronApp = App< + BankKeeper, + MockApi, + MemoryStorage, + FailingModule, + WasmKeeper, + StakeKeeper, + DistributionKeeper, + FailingModule, + FailingModule, + NeutronStargate, +>; + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Helper { + #[derivative(Debug = "ignore")] + pub app: NeutronApp, + pub owner: Addr, + pub assets: HashMap, + pub factory: Addr, + pub pair_addr: Addr, + pub lp_token: String, + pub fake_maker: Addr, + pub native_coin_registry: Addr, +} + +impl Helper { + pub fn new( + owner: &Addr, + test_coins: Vec, + params: ConcentratedPoolParams, + ) -> AnyResult { + let mut app = BasicAppBuilder::new() + .with_stargate(NeutronStargate::default()) + .build(no_init); + + let asset_infos_vec = test_coins + .iter() + .cloned() + .map(|coin| { + let asset_info = match &coin { + TestCoin::Native(denom) | TestCoin::NativePrecise(denom, ..) => { + native_asset_info(denom.clone()) + } + _ => unimplemented!(), + }; + (coin, asset_info) + }) + .collect::>(); + + let pcl_code_id = app.store_code(pcl_duality_contract()); + let factory_code_id = app.store_code(factory_contract()); + let pair_type = PairType::Custom("concentrated_duality_orderbook".to_string()); + + let fake_maker = Addr::unchecked("fake_maker"); + + let coin_registry_id = app.store_code(coin_registry_contract()); + + let native_coin_registry = app + .instantiate_contract( + coin_registry_id, + owner.clone(), + &astroport::native_coin_registry::InstantiateMsg { + owner: owner.to_string(), + }, + &[], + "Coin registry", + None, + ) + .unwrap(); + + // Register decimals + test_coins.iter().for_each(|test_coin| { + if let Some(denom) = test_coin.denom() { + register_decimals( + &mut app, + &native_coin_registry, + &denom, + test_coin.decimals(), + ) + .unwrap(); + } + }); + + let init_msg = astroport::factory::InstantiateMsg { + fee_address: Some(fake_maker.to_string()), + pair_configs: vec![PairConfig { + code_id: pcl_code_id, + maker_fee_bps: 5000, + total_fee_bps: 0u16, // Concentrated pair does not use this field, + pair_type: pair_type.clone(), + is_disabled: false, + is_generator_disabled: false, + permissioned: false, + }], + token_code_id: 0, + generator_address: None, + owner: owner.to_string(), + whitelist_code_id: 0, + coin_registry_address: native_coin_registry.to_string(), + tracker_config: None, + }; + + let factory = app.instantiate_contract( + factory_code_id, + owner.clone(), + &init_msg, + &[], + "FACTORY", + None, + )?; + + let asset_infos = asset_infos_vec + .clone() + .into_iter() + .map(|(_, asset_info)| asset_info) + .collect_vec(); + + let init_pair_msg: astroport::factory::ExecuteMsg = + astroport::factory::ExecuteMsg::CreatePair { + pair_type, + asset_infos: asset_infos.clone(), + init_params: Some( + to_json_binary(&ConcentratedDualityParams { + main_params: params, + orderbook_config: OrderbookConfig { + enable: false, + liquidity_percent: Decimal::percent(20), + orders_number: 5, + min_asset_0_order_size: Uint128::from(1000u128), + min_asset_1_order_size: Uint128::from(1000u128), + executor: Some(owner.to_string()), + }, + }) + .unwrap(), + ), + }; + + app.execute_contract(owner.clone(), factory.clone(), &init_pair_msg, &[])?; + + let resp: PairInfo = app.wrap().query_wasm_smart( + &factory, + &astroport::factory::QueryMsg::Pair { asset_infos }, + )?; + + Ok(Self { + app, + owner: owner.clone(), + assets: asset_infos_vec.into_iter().collect(), + factory, + pair_addr: resp.contract_addr, + lp_token: resp.liquidity_token, + fake_maker, + native_coin_registry, + }) + } + + pub fn provide_liquidity(&mut self, sender: &Addr, assets: &[Asset]) -> AnyResult { + self.provide_liquidity_with_slip_tolerance( + sender, + assets, + Some(f64_to_dec(0.5)), // 50% slip tolerance for testing purposes + ) + } + + pub fn provide_liquidity_with_slip_tolerance( + &mut self, + sender: &Addr, + assets: &[Asset], + slippage_tolerance: Option, + ) -> AnyResult { + let funds = assets + .iter() + .map(|asset| asset.as_coin().unwrap()) + .collect_vec(); + + let msg = ExecuteMsg::ProvideLiquidity { + assets: assets.to_vec(), + slippage_tolerance, + auto_stake: None, + receiver: None, + min_lp_to_receive: None, + }; + + self.app + .execute_contract(sender.clone(), self.pair_addr.clone(), &msg, &funds) + } + + pub fn withdraw_liquidity( + &mut self, + sender: &Addr, + amount: u128, + assets: Vec, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.pair_addr.clone(), + &ExecuteMsg::WithdrawLiquidity { + assets, + min_assets_to_receive: None, + }, + &[coin(amount, self.lp_token.to_string())], + ) + } + + pub fn swap( + &mut self, + sender: &Addr, + offer_asset: &Asset, + max_spread: Option, + ) -> AnyResult { + self.swap_full_params(sender, offer_asset, max_spread, None) + } + + pub fn swap_full_params( + &mut self, + sender: &Addr, + offer_asset: &Asset, + max_spread: Option, + belief_price: Option, + ) -> AnyResult { + match &offer_asset.info { + AssetInfo::Token { .. } => unimplemented!(), + AssetInfo::NativeToken { .. } => { + let msg = ExecuteMsg::Swap { + offer_asset: offer_asset.clone(), + ask_asset_info: None, + belief_price, + max_spread, + to: None, + }; + + self.app.execute_contract( + sender.clone(), + self.pair_addr.clone(), + &msg, + &[offer_asset.as_coin().unwrap()], + ) + } + } + } + + pub fn simulate_swap( + &self, + offer_asset: &Asset, + ask_asset_info: Option, + ) -> StdResult { + self.app.wrap().query_wasm_smart( + &self.pair_addr, + &QueryMsg::Simulation { + offer_asset: offer_asset.clone(), + ask_asset_info, + }, + ) + } + + pub fn simulate_reverse_swap( + &self, + ask_asset: &Asset, + offer_asset_info: Option, + ) -> StdResult { + self.app.wrap().query_wasm_smart( + &self.pair_addr, + &QueryMsg::ReverseSimulation { + ask_asset: ask_asset.clone(), + offer_asset_info, + }, + ) + } + + pub fn query_prices(&self) -> StdResult { + self.app + .wrap() + .query_wasm_smart(&self.pair_addr, &QueryMsg::CumulativePrices {}) + } + + pub fn native_balance(&self, denom: &str, user: &Addr) -> u128 { + self.app + .wrap() + .query_balance(user, denom) + .unwrap() + .amount + .u128() + } + + pub fn coin_balance(&self, coin: &TestCoin, user: &Addr) -> u128 { + match &self.assets[coin] { + AssetInfo::Token { .. } => unimplemented!(), + AssetInfo::NativeToken { denom } => self.native_balance(denom, user), + } + } + + pub fn give_me_money(&mut self, assets: &[Asset], recipient: &Addr) { + assets.iter().for_each(|asset| { + if !asset.amount.is_zero() { + self.app + .sudo( + BankSudo::Mint { + to_address: recipient.to_string(), + amount: vec![asset.as_coin().unwrap()], + } + .into(), + ) + .unwrap(); + } + }); + } + + pub fn query_config(&self) -> StdResult { + let binary = self + .app + .wrap() + .query_wasm_raw(&self.pair_addr, b"config")? + .ok_or_else(|| StdError::generic_err("Failed to find config in storage"))?; + from_json(&binary) + } + + pub fn query_ob_config(&self) -> StdResult { + let binary = self + .app + .wrap() + .query_wasm_raw(&self.pair_addr, b"orderbook_config")? + .ok_or_else(|| StdError::generic_err("Failed to find orderbook_config in storage"))?; + from_json(&binary) + } + + pub fn query_pool(&self) -> StdResult { + self.app + .wrap() + .query_wasm_smart(&self.pair_addr, &QueryMsg::Pool {}) + } + + pub fn query_lp_price(&self) -> StdResult { + self.app + .wrap() + .query_wasm_smart(&self.pair_addr, &QueryMsg::LpPrice {}) + } + + pub fn update_config( + &mut self, + user: &Addr, + action: &ConcentratedPoolUpdateParams, + ) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.pair_addr.clone(), + &ExecuteMsg::UpdateConfig { + params: to_json_binary(action).unwrap(), + }, + &[], + ) + } + + pub fn query_amp_gamma(&self) -> StdResult { + let config_resp: ConfigResponse = self + .app + .wrap() + .query_wasm_smart(&self.pair_addr, &QueryMsg::Config {})?; + let params: ConcentratedPoolConfig = from_json( + &config_resp + .params + .ok_or_else(|| StdError::generic_err("Params not found in config response!"))?, + )?; + Ok(AmpGammaResponse { + amp: params.amp, + gamma: params.gamma, + future_time: self.query_config()?.pool_state.future_time, + }) + } + + pub fn query_d(&self) -> StdResult { + self.app + .wrap() + .query_wasm_smart(&self.pair_addr, &QueryMsg::ComputeD {}) + } + + pub fn query_share(&self, amount: impl Into) -> StdResult> { + self.app.wrap().query_wasm_smart::>( + &self.pair_addr, + &QueryMsg::Share { + amount: amount.into(), + }, + ) + } + + pub fn observe_price(&self, seconds_ago: u64) -> StdResult { + self.app + .wrap() + .query_wasm_smart::( + &self.pair_addr, + &QueryMsg::Observe { seconds_ago }, + ) + .map(|val| val.price) + } + + pub fn next_block(&mut self, time: u64) { + self.app.update_block(|block| { + block.time = block.time.plus_seconds(time); + block.height += 1 + }); + } +} + +pub fn register_decimals( + app: &mut NeutronApp, + registry_contract: &Addr, + denom: &str, + decimals: u8, +) -> AnyResult { + let registrator = app.api().addr_make("registrator"); + let funds = coins(1, denom); + + app.sudo( + BankSudo::Mint { + to_address: registrator.to_string(), + amount: funds.clone(), + } + .into(), + ) + .unwrap(); + + app.execute_contract( + registrator.clone(), + registry_contract.clone(), + &astroport::native_coin_registry::ExecuteMsg::Register { + native_coins: vec![(denom.to_string(), decimals)], + }, + &funds, + ) + .unwrap(); + + app.execute(registrator, BankMsg::Burn { amount: funds }.into()) +} diff --git a/contracts/pair_concentrated_duality/tests/common/mod.rs b/contracts/pair_concentrated_duality/tests/common/mod.rs new file mode 100644 index 000000000..1fa83b9ff --- /dev/null +++ b/contracts/pair_concentrated_duality/tests/common/mod.rs @@ -0,0 +1,4 @@ +pub mod helper; + +pub mod astroport_wrapper; +pub mod neutron_wrapper; diff --git a/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs b/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs new file mode 100644 index 000000000..d26d08fe5 --- /dev/null +++ b/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs @@ -0,0 +1,236 @@ +#![cfg(not(tarpaulin_include))] +#![cfg(feature = "test-tube")] +#![allow(dead_code)] + +use std::collections::HashMap; +use std::path::Path; +use std::{process::Command, str::FromStr}; + +use anyhow::Result as AnyResult; +use cosmwasm_schema::serde::de::{DeserializeOwned, Error}; +use cosmwasm_schema::serde::Serialize; +use cosmwasm_std::{coin, Coin, Decimal256, Event, Fraction, Uint128}; +use neutron_test_tube::{ + Account, Bank, Dex, ExecuteResponse, Module, NeutronTestApp, SigningAccount, Wasm, +}; + +const BUILD_CONTRACTS: &[&str] = &[ + "astroport-pair-concentrated-duality", + "astroport-pair-concentrated", + "astroport-factory", + "astroport-native-coin-registry", +]; + +fn locate_workspace_root() -> String { + let result = Command::new("cargo") + .args(&["locate-project", "--workspace", "--message-format=plain"]) + .output() + .expect("failed to locate workspace root"); + + String::from_utf8(result.stdout) + .unwrap() + .trim_end() + .strip_suffix("Cargo.toml") + .unwrap() + .to_string() +} + +pub struct TestAppWrapper<'a> { + pub signer: SigningAccount, + pub app: &'a NeutronTestApp, + pub wasm: Wasm<'a, NeutronTestApp>, + pub bank: Bank<'a, NeutronTestApp>, + pub dex: Dex<'a, NeutronTestApp>, + pub code_ids: HashMap<&'a str, u64>, +} + +impl<'a> TestAppWrapper<'a> { + pub fn bootstrap(app: &'a NeutronTestApp) -> AnyResult { + let project_dir = locate_workspace_root(); + + // Build contracts + for contract in BUILD_CONTRACTS { + let output = Command::new("cargo") + .args(&[ + "build", + "--target", + "wasm32-unknown-unknown", + "--release", + "--lib", + "--locked", + "--package", + contract, + ]) + .current_dir(&project_dir) + .output() + .expect(&format!("failed to build contract {}", contract)); + assert!( + output.status.success(), + "failed to build contracts: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + let target_dir = Path::new(&project_dir).join("target/wasm32-unknown-unknown/release"); + + let native_registry_wasm = target_dir.join("astroport_native_coin_registry.wasm"); + let factory_wasm = target_dir.join("astroport_factory.wasm"); + let cl_pool_wasm = target_dir.join("astroport_pair_concentrated.wasm"); + let cl_pool_duality_wasm = target_dir.join("astroport_pair_concentrated_duality.wasm"); + + let signer = app + .init_account(&[coin(10000e18 as u128, "untrn")]) + .unwrap(); + + let mut helper = Self { + signer, + app, + wasm: Wasm::new(app), + dex: Dex::new(app), + bank: Bank::new(&app), + code_ids: HashMap::new(), + }; + + // Store Astroport contracts + + println!("Storing cl pool contract..."); + let cl_pair_code_id = helper.store_code(&cl_pool_wasm)?; + helper.code_ids.insert("pair-concentrated", cl_pair_code_id); + + println!("Storing cl pool duality contract..."); + let cl_pair_inj_code_id = helper.store_code(&cl_pool_duality_wasm)?; + helper + .code_ids + .insert("pair-concentrated-duality", cl_pair_inj_code_id); + + println!("Storing coin registry contract..."); + let native_registry_code_id = helper.store_code(&native_registry_wasm)?; + helper + .code_ids + .insert("coin-registry", native_registry_code_id); + + println!("Storing factory contract..."); + let factory_code_id = helper.store_code(&factory_wasm)?; + helper.code_ids.insert("factory", factory_code_id); + + Ok(helper) + } + + pub fn store_code

(&self, contract_path: P) -> AnyResult + where + P: AsRef, + { + // Load the contract wasm bytecode + let wasm_byte_code = std::fs::read(contract_path)?; + + // Store the code + self.wasm + .store_code(&wasm_byte_code, None, &self.signer) + .map(|res| res.data.code_id) + .map_err(Into::into) + } + + pub fn store_and_init(&self, contract_path: P, instantiate_msg: &T) -> AnyResult + where + T: ?Sized + Serialize, + P: AsRef, + { + let code_id = self.store_code(contract_path)?; + + // Instantiate the contract + self.init_contract(code_id, instantiate_msg, &[]) + } + + pub fn init_contract(&self, code_id: u64, msg: &T, funds: &[Coin]) -> AnyResult + where + T: ?Sized + Serialize, + { + self.wasm + .instantiate( + code_id, + msg, + Some(&self.signer.address()), + Some("Test label"), + funds, + &self.signer, + ) + .map(|res| res.data.address.to_string()) + .map_err(|e| e.into()) + } + + pub fn execute_contract( + &self, + sender: &SigningAccount, + contract_addr: &str, + msg: &impl Serialize, + funds: &[Coin], + ) -> AnyResult< + ExecuteResponse< + neutron_test_tube::cosmrs::proto::cosmwasm::wasm::v1::MsgExecuteContractResponse, + >, + > { + self.wasm + .execute(contract_addr, msg, funds, sender) + .map_err(Into::into) + } + + pub fn smart_query(&self, contract: &str, query: &T) -> AnyResult + where + T: ?Sized + Serialize, + R: ?Sized + DeserializeOwned, + { + self.wasm.query(contract, query).map_err(Into::into) + } + + pub fn next_block(&self) -> () { + self.app.increase_time(5) + } +} + +fn find_attribute(events: &[Event], key: &str) -> Option { + for event in events { + for attr in &event.attributes { + if attr.key == key { + return Some(attr.value.to_string()); + } + } + } + + None +} + +pub fn f64_to_dec(val: f64) -> T +where + T: FromStr, + T::Err: Error, +{ + T::from_str(&val.to_string()).unwrap() +} + +pub struct SdkDec { + pub value: T, +} + +impl SdkDec { + pub fn new(value: T) -> Self { + Self { value } + } +} + +impl Into for SdkDec { + fn into(self) -> String { + self.value.atomics().to_string() + } +} + +impl Into for SdkDec { + fn into(self) -> u128 { + let uint128: Uint128 = self.value.numerator().try_into().unwrap(); + uint128.u128() + } +} + +impl From for SdkDec { + fn from(val: f64) -> Self { + Self::new(Decimal256::from_str(&val.to_string()).unwrap()) + } +} diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs new file mode 100644 index 000000000..0814f7107 --- /dev/null +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs @@ -0,0 +1,35 @@ +#![cfg(not(tarpaulin_include))] +#![cfg(feature = "test-tube")] +#![allow(dead_code)] + +use cosmwasm_std::{Decimal, Uint128}; +use neutron_test_tube::{Account, NeutronTestApp}; + +use astroport::pair_concentrated_duality::OrderbookConfig; +use astroport_test::coins::TestCoin; +use common::{ + astroport_wrapper::AstroportHelper, helper::common_pcl_params, neutron_wrapper::TestAppWrapper, +}; + +mod common; + +#[test] +fn init_on_duality() { + let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("astro")]; + let app = NeutronTestApp::new(); + let neutron = TestAppWrapper::bootstrap(&app).unwrap(); + let owner = neutron.signer.address(); + let _astroport = AstroportHelper::new( + neutron, + test_coins, + common_pcl_params(), + OrderbookConfig { + enable: true, + executor: Some(owner), + liquidity_percent: Decimal::percent(20), + orders_number: 5, + min_asset_0_order_size: Uint128::from(1_000u128), + min_asset_1_order_size: Uint128::from(1_000u128), + }, + ); +} diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs new file mode 100644 index 000000000..82aea0633 --- /dev/null +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs @@ -0,0 +1,1117 @@ +#![cfg(not(tarpaulin_include))] + +use cosmwasm_std::{Addr, Decimal, Decimal256}; +use itertools::{max, Itertools}; + +use astroport::asset::{native_asset_info, AssetInfoExt, MINIMUM_LIQUIDITY_AMOUNT}; +use astroport::cosmwasm_ext::IntegerToDecimal; +use astroport::pair_concentrated::{ + ConcentratedPoolParams, ConcentratedPoolUpdateParams, PromoteParams, UpdatePoolParams, +}; +use astroport::pair_concentrated_duality::{DualityPairMsg, UpdateDualityOrderbook}; +use astroport_pair_concentrated_duality::error::ContractError; +use astroport_pair_concentrated_duality::orderbook::error::OrderbookError; +use astroport_pcl_common::consts::{AMP_MAX, AMP_MIN, MA_HALF_TIME_LIMITS}; +use astroport_pcl_common::error::PclError; +use astroport_test::coins::TestCoin; +use astroport_test::convert::{dec_to_f64, f64_to_dec}; +use astroport_test::cw_multi_test::Executor; + +use crate::common::helper::{common_pcl_params, ExecuteMsg, Helper}; + +mod common; + +#[test] +fn check_wrong_initialization() { + let owner = Addr::unchecked("owner"); + + let params = common_pcl_params(); + + let mut wrong_params = params.clone(); + wrong_params.amp = Decimal::zero(); + + let err = Helper::new( + &owner, + vec![TestCoin::native("untrn"), TestCoin::native("ASTRO")], + wrong_params, + ) + .unwrap_err(); + + assert_eq!( + ContractError::PclError(PclError::IncorrectPoolParam( + "amp".to_string(), + AMP_MIN.to_string(), + AMP_MAX.to_string() + )), + err.downcast().unwrap(), + ); + + let mut wrong_params = params.clone(); + wrong_params.ma_half_time = MA_HALF_TIME_LIMITS.end() + 1; + + let err = Helper::new( + &owner, + vec![TestCoin::native("untrn"), TestCoin::native("ASTRO")], + wrong_params, + ) + .unwrap_err(); + + assert_eq!( + ContractError::PclError(PclError::IncorrectPoolParam( + "ma_half_time".to_string(), + MA_HALF_TIME_LIMITS.start().to_string(), + MA_HALF_TIME_LIMITS.end().to_string() + )), + err.downcast().unwrap(), + ); + + let mut wrong_params = params.clone(); + wrong_params.price_scale = Decimal::zero(); + + let err = Helper::new( + &owner, + vec![TestCoin::native("untrn"), TestCoin::native("ASTRO")], + wrong_params, + ) + .unwrap_err(); + + assert_eq!( + err.root_cause().to_string(), + "Generic error: Initial price scale can not be zero", + ); + + // check instantiation with valid params + Helper::new( + &owner, + vec![TestCoin::native("untrn"), TestCoin::native("ASTRO")], + params, + ) + .unwrap(); +} + +#[test] +fn provide_and_withdraw() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("uluna"), TestCoin::native("uusdc")]; + + let params = ConcentratedPoolParams { + price_scale: Decimal::from_ratio(2u8, 1u8), + ..common_pcl_params() + }; + + let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + + // checking LP token virtual price on an empty pool + let lp_price = helper.query_lp_price().unwrap(); + assert!( + lp_price.is_zero(), + "LP price must be zero before any provide" + ); + + let user1 = Addr::unchecked("user1"); + + let random_coin = native_asset_info("random-coin".to_string()).with_balance(100u8); + let wrong_assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_000000u128), + random_coin.clone(), + ]; + + helper.give_me_money(&wrong_assets, &user1); + + // Provide with empty assets + let err = helper.provide_liquidity(&user1, &[]).unwrap_err(); + assert_eq!( + "Generic error: Nothing to provide", + err.root_cause().to_string() + ); + + // Provide just one asset which does not belong to the pair + let err = helper + .provide_liquidity(&user1, &[random_coin.clone()]) + .unwrap_err(); + assert_eq!( + "The asset random-coin does not belong to the pair", + err.root_cause().to_string() + ); + + helper.give_me_money(&[helper.assets[&test_coins[1]].with_balance(1u8)], &user1); + + // Try to provide 3 assets + let err = helper + .provide_liquidity( + &user1, + &[ + random_coin.clone(), + helper.assets[&test_coins[0]].with_balance(1u8), + helper.assets[&test_coins[1]].with_balance(1u8), + ], + ) + .unwrap_err(); + assert_eq!( + ContractError::InvalidNumberOfAssets(2), + err.downcast().unwrap() + ); + + helper.give_me_money( + &[helper.assets[&test_coins[1]].with_balance(50_000_000000u128 - 1)], + &user1, + ); + + // Try to provide with zero amount + let err = helper + .provide_liquidity( + &user1, + &[ + helper.assets[&test_coins[0]].with_balance(0u8), + helper.assets[&test_coins[1]].with_balance(50_000_000000u128), + ], + ) + .unwrap_err(); + assert_eq!(ContractError::InvalidZeroAmount {}, err.downcast().unwrap()); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_000000u128), + helper.assets[&test_coins[1]].with_balance(50_000_000000u128), + ]; + + // Test very small initial provide + let err = helper + .provide_liquidity( + &user1, + &[ + helper.assets[&test_coins[0]].with_balance(1000u128), + helper.assets[&test_coins[1]].with_balance(500u128), + ], + ) + .unwrap_err(); + assert_eq!( + ContractError::MinimumLiquidityAmountError {}, + err.downcast().unwrap() + ); + + // This is normal provision + helper.provide_liquidity(&user1, &assets).unwrap(); + + assert_eq!( + 70710_677118, + helper.native_balance(&helper.lp_token, &user1) + ); + assert_eq!(0, helper.coin_balance(&test_coins[0], &user1)); + assert_eq!(0, helper.coin_balance(&test_coins[1], &user1)); + + assert_eq!( + helper + .query_share(helper.native_balance(&helper.lp_token, &user1)) + .unwrap(), + vec![ + helper.assets[&test_coins[0]].with_balance(99999998584u128), + helper.assets[&test_coins[1]].with_balance(49999999292u128) + ] + ); + + let user2 = Addr::unchecked("user2"); + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_000000u128), + helper.assets[&test_coins[1]].with_balance(50_000_000000u128), + ]; + helper.give_me_money(&assets, &user2); + helper.provide_liquidity(&user2, &assets).unwrap(); + assert_eq!( + 70710_677118 + MINIMUM_LIQUIDITY_AMOUNT.u128(), + helper.native_balance(&helper.lp_token, &user2) + ); + + // Changing order of assets does not matter + let user3 = Addr::unchecked("user3"); + let assets = vec![ + helper.assets[&test_coins[1]].with_balance(50_000_000000u128), + helper.assets[&test_coins[0]].with_balance(100_000_000000u128), + ]; + helper.give_me_money(&assets, &user3); + helper.provide_liquidity(&user3, &assets).unwrap(); + assert_eq!( + 70710_677118 + MINIMUM_LIQUIDITY_AMOUNT.u128(), + helper.native_balance(&helper.lp_token, &user3) + ); + + // After initial provide one-sided provide is allowed + let user4 = Addr::unchecked("user4"); + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(0u128), + helper.assets[&test_coins[1]].with_balance(100_000_000000u128), + ]; + helper.give_me_money(&assets, &user4); + helper.provide_liquidity(&user4, &assets).unwrap(); + // LP amount is less than for prev users as provide is imbalanced + assert_eq!( + 62217_722016, + helper.native_balance(&helper.lp_token, &user4) + ); + + // One of assets may be omitted + let user5 = Addr::unchecked("user5"); + let assets = vec![helper.assets[&test_coins[0]].with_balance(140_000_000000u128)]; + helper.give_me_money(&assets, &user5); + helper.provide_liquidity(&user5, &assets).unwrap(); + assert_eq!( + 57271_023590, + helper.native_balance(&helper.lp_token, &user5) + ); + + // check that imbalanced withdraw is currently disabled + let withdraw_assets = vec![ + helper.assets[&test_coins[0]].with_balance(10_000_000000u128), + helper.assets[&test_coins[1]].with_balance(5_000_000000u128), + ]; + let err = helper + .withdraw_liquidity(&user1, 7071_067711, withdraw_assets) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Imbalanced withdraw is currently disabled" + ); + + // user1 withdraws 1/10 of his LP tokens + helper + .withdraw_liquidity(&user1, 7071_067711, vec![]) + .unwrap(); + + assert_eq!( + 70710_677118 - 7071_067711, + helper.native_balance(&helper.lp_token, &user1) + ); + assert_eq!(9382_010960, helper.coin_balance(&test_coins[0], &user1)); + assert_eq!(5330_688045, helper.coin_balance(&test_coins[1], &user1)); + + // user2 withdraws half + helper + .withdraw_liquidity(&user2, 35355_339059, vec![]) + .unwrap(); + + assert_eq!( + 70710_677118 + MINIMUM_LIQUIDITY_AMOUNT.u128() - 35355_339059, + helper.native_balance(&helper.lp_token, &user2) + ); + assert_eq!(46910_055478, helper.coin_balance(&test_coins[0], &user2)); + assert_eq!(26653_440612, helper.coin_balance(&test_coins[1], &user2)); +} + +#[test] +fn check_imbalanced_provide() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("uusd")]; + + let mut params = ConcentratedPoolParams { + price_scale: Decimal::from_ratio(2u8, 1u8), + ..common_pcl_params() + }; + + let mut helper = Helper::new(&owner, test_coins.clone(), params.clone()).unwrap(); + + let user1 = Addr::unchecked("user1"); + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_000000u128), + helper.assets[&test_coins[1]].with_balance(100_000_000000u128), + ]; + helper.give_me_money(&assets, &user1); + helper.provide_liquidity(&user1, &assets).unwrap(); + + assert_eq!( + 100285_256937, + helper.native_balance(&helper.lp_token, &user1) + ); + assert_eq!(0, helper.coin_balance(&test_coins[0], &user1)); + assert_eq!(0, helper.coin_balance(&test_coins[1], &user1)); + + // creating a new pool with inverted price scale + params.price_scale = Decimal::from_ratio(1u8, 2u8); + + let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_000000u128), + helper.assets[&test_coins[1]].with_balance(100_000_000000u128), + ]; + helper.give_me_money(&assets, &user1); + helper.provide_liquidity(&user1, &assets).unwrap(); + + assert_eq!( + 100285_256937, + helper.native_balance(&helper.lp_token, &user1) + ); + assert_eq!(0, helper.coin_balance(&test_coins[0], &user1)); + assert_eq!(0, helper.coin_balance(&test_coins[1], &user1)); +} + +#[test] +fn provide_with_different_precision() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![ + TestCoin::native_precise("foo", 5), + TestCoin::native("untrn"), + ]; + + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_00000u128), + helper.assets[&test_coins[1]].with_balance(100_000000u128), + ]; + helper.give_me_money(&assets, &owner); + helper.provide_liquidity(&owner, &assets).unwrap(); + + let tolerance = 9; + + for user_name in ["user1", "user2", "user3"] { + let user = Addr::unchecked(user_name); + + helper.give_me_money(&assets, &user); + + helper.provide_liquidity(&user, &assets).unwrap(); + + let lp_amount = helper.native_balance(&helper.lp_token, &user); + assert!( + 100_000000 - lp_amount < tolerance, + "LP token balance assert failed for {user}" + ); + assert_eq!(0, helper.coin_balance(&test_coins[0], &user)); + assert_eq!(0, helper.coin_balance(&test_coins[1], &user)); + + helper.withdraw_liquidity(&user, lp_amount, vec![]).unwrap(); + + assert_eq!(0, helper.native_balance(&helper.lp_token, &user)); + assert!( + 100_00000 - helper.coin_balance(&test_coins[0], &user) < tolerance, + "Withdrawn amount of coin0 assert failed for {user}" + ); + assert!( + 100_000000 - helper.coin_balance(&test_coins[1], &user) < tolerance, + "Withdrawn amount of coin1 assert failed for {user}" + ); + } +} + +#[test] +fn swap_different_precisions() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![ + TestCoin::native_precise("foo", 5), + TestCoin::native("untrn"), + ]; + + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_00000u128), + helper.assets[&test_coins[1]].with_balance(100_000_000000u128), + ]; + helper.give_me_money(&assets, &owner); + helper.provide_liquidity(&owner, &assets).unwrap(); + + let user = Addr::unchecked("user"); + // 100 x FOO tokens + let offer_asset = helper.assets[&test_coins[0]].with_balance(100_00000u128); + + // Checking direct swap simulation + let sim_resp = helper.simulate_swap(&offer_asset, None).unwrap(); + // And reverse swap as well + let reverse_sim_resp = helper + .simulate_reverse_swap( + &helper.assets[&test_coins[1]].with_balance(sim_resp.return_amount.u128()), + None, + ) + .unwrap(); + assert_eq!(reverse_sim_resp.offer_amount.u128(), 10019003); + assert_eq!(reverse_sim_resp.commission_amount.u128(), 45084); + assert_eq!(reverse_sim_resp.spread_amount.u128(), 125); + + helper.give_me_money(&[offer_asset.clone()], &user); + helper.swap(&user, &offer_asset, None).unwrap(); + + assert_eq!(0, helper.coin_balance(&test_coins[0], &user)); + // 99_737929 x BAR tokens + assert_eq!(99_737929, sim_resp.return_amount.u128()); + assert_eq!( + sim_resp.return_amount.u128(), + helper.coin_balance(&test_coins[1], &user) + ); +} + +#[test] +fn check_reverse_swap() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("uusd")]; + + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_000000u128), + helper.assets[&test_coins[1]].with_balance(100_000_000000u128), + ]; + helper.give_me_money(&assets, &owner); + helper.provide_liquidity(&owner, &assets).unwrap(); + + let offer_asset = helper.assets[&test_coins[0]].with_balance(50_000_000000u128); + + let sim_resp = helper.simulate_swap(&offer_asset, None).unwrap(); + let reverse_sim_resp = helper + .simulate_reverse_swap( + &helper.assets[&test_coins[1]].with_balance(sim_resp.return_amount.u128()), + None, + ) + .unwrap(); + assert_eq!(reverse_sim_resp.offer_amount.u128(), 50000220879u128); // as it is hard to predict dynamic fees reverse swap is not exact + assert_eq!(reverse_sim_resp.commission_amount.u128(), 151_913981); + assert_eq!(reverse_sim_resp.spread_amount.u128(), 16241_558397); +} + +#[test] +fn check_swaps_simple() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("uusd")]; + + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); + + let user = Addr::unchecked("user"); + let offer_asset = helper.assets[&test_coins[0]].with_balance(100_000000u128); + helper.give_me_money(&[offer_asset.clone()], &user); + + // Check swap does not work if pool is empty + let err = helper.swap(&user, &offer_asset, None).unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: One of the pools is empty" + ); + + // Try to swap a wrong asset + let wrong_coin = native_asset_info("random-coin".to_string()); + let wrong_asset = wrong_coin.with_balance(100_000000u128); + helper.give_me_money(&[wrong_asset.clone()], &user); + let err = helper.swap(&user, &wrong_asset, None).unwrap_err(); + assert_eq!( + ContractError::InvalidAsset(wrong_coin.to_string()), + err.downcast().unwrap() + ); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_000000u128), + helper.assets[&test_coins[1]].with_balance(100_000_000000u128), + ]; + helper.give_me_money(&assets, &owner); + helper.provide_liquidity(&owner, &assets).unwrap(); + + let d = helper.query_d().unwrap(); + assert_eq!(dec_to_f64(d), 200000f64); + + helper.swap(&user, &offer_asset, None).unwrap(); + assert_eq!(0, helper.coin_balance(&test_coins[0], &user)); + assert_eq!(99_737929, helper.coin_balance(&test_coins[1], &user)); + + let offer_asset = helper.assets[&test_coins[0]].with_balance(90_000_000000u128); + helper.give_me_money(&[offer_asset.clone()], &user); + let err = helper.swap(&user, &offer_asset, None).unwrap_err(); + assert_eq!( + ContractError::PclError(PclError::MaxSpreadAssertion {}), + err.downcast().unwrap() + ); + + let user2 = Addr::unchecked("user2"); + let offer_asset = helper.assets[&test_coins[1]].with_balance(100_000000u128); + helper.give_me_money(&[offer_asset.clone()], &user2); + helper.swap(&user2, &offer_asset, None).unwrap(); + assert_eq!(0, helper.coin_balance(&test_coins[1], &user2)); + assert_eq!(99_741246, helper.coin_balance(&test_coins[0], &user2)); + + let d = helper.query_d().unwrap(); + assert_eq!(dec_to_f64(d), 200000.260415); +} + +#[test] +fn check_swaps_with_price_update() { + let owner = Addr::unchecked("owner"); + let half = Decimal::from_ratio(1u8, 2u8); + + let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("uusd")]; + + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); + + helper.next_block(1000); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_000000u128), + helper.assets[&test_coins[1]].with_balance(100_000_000000u128), + ]; + helper.give_me_money(&assets, &owner); + helper.provide_liquidity(&owner, &assets).unwrap(); + + helper.next_block(1000); + + let user1 = Addr::unchecked("user1"); + let offer_asset = helper.assets[&test_coins[1]].with_balance(10_000_000000u128); + let mut prev_vlp_price = helper.query_lp_price().unwrap(); + + for i in 0..4 { + helper.give_me_money(&[offer_asset.clone()], &user1); + helper.swap(&user1, &offer_asset, Some(half)).unwrap(); + let new_vlp_price = helper.query_lp_price().unwrap(); + assert!( + new_vlp_price >= prev_vlp_price, + "{i}: new_vlp_price <= prev_vlp_price ({new_vlp_price} <= {prev_vlp_price})", + ); + prev_vlp_price = new_vlp_price; + helper.next_block(1000); + } + + let offer_asset = helper.assets[&test_coins[0]].with_balance(10_000_000000u128); + for _i in 0..4 { + helper.give_me_money(&[offer_asset.clone()], &user1); + helper.swap(&user1, &offer_asset, Some(half)).unwrap(); + helper.next_block(1000); + } +} + +#[test] +fn provides_and_swaps() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("uusd")]; + + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); + + helper.next_block(1000); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_000000u128), + helper.assets[&test_coins[1]].with_balance(100_000_000000u128), + ]; + helper.give_me_money(&assets, &owner); + helper.provide_liquidity(&owner, &assets).unwrap(); + + helper.next_block(1000); + + let user = Addr::unchecked("user"); + let offer_asset = helper.assets[&test_coins[0]].with_balance(100_000000u128); + helper.give_me_money(&[offer_asset.clone()], &user); + helper.swap(&user, &offer_asset, None).unwrap(); + + let provider = Addr::unchecked("provider"); + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(1_000_000000u128), + helper.assets[&test_coins[1]].with_balance(1_000_000000u128), + ]; + helper.give_me_money(&assets, &provider); + helper.provide_liquidity(&provider, &assets).unwrap(); + + let offer_asset = helper.assets[&test_coins[1]].with_balance(100_000000u128); + helper.give_me_money(&[offer_asset.clone()], &user); + helper.swap(&user, &offer_asset, None).unwrap(); + + helper + .withdraw_liquidity(&provider, 999_999354, vec![]) + .unwrap(); + + let offer_asset = helper.assets[&test_coins[0]].with_balance(100_000000u128); + helper.give_me_money(&[offer_asset.clone()], &user); + helper.swap(&user, &offer_asset, None).unwrap(); +} + +#[test] +fn check_amp_gamma_change() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("uusd")]; + + let params = ConcentratedPoolParams { + amp: f64_to_dec(40f64), + gamma: f64_to_dec(0.0001), + ..common_pcl_params() + }; + let mut helper = Helper::new(&owner, test_coins, params).unwrap(); + + let random_user = Addr::unchecked("random"); + let action = ConcentratedPoolUpdateParams::Update(UpdatePoolParams { + mid_fee: Some(f64_to_dec(0.002)), + out_fee: None, + fee_gamma: None, + repeg_profit_threshold: None, + min_price_scale_delta: None, + ma_half_time: None, + }); + + let err = helper.update_config(&random_user, &action).unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + + helper.update_config(&owner, &action).unwrap(); + + helper.next_block(86400); + + let future_time = helper.app.block_info().time.seconds() + 100_000; + let target_amp = 44f64; + let target_gamma = 0.00009; + let action = ConcentratedPoolUpdateParams::Promote(PromoteParams { + next_amp: f64_to_dec(target_amp), + next_gamma: f64_to_dec(target_gamma), + future_time, + }); + helper.update_config(&owner, &action).unwrap(); + + let amp_gamma = helper.query_amp_gamma().unwrap(); + assert_eq!(dec_to_f64(amp_gamma.amp), 40f64); + assert_eq!(dec_to_f64(amp_gamma.gamma), 0.0001); + assert_eq!(amp_gamma.future_time, future_time); + + helper.next_block(50_000); + + let amp_gamma = helper.query_amp_gamma().unwrap(); + assert_eq!(dec_to_f64(amp_gamma.amp), 42f64); + assert_eq!(dec_to_f64(amp_gamma.gamma), 0.000095); + assert_eq!(amp_gamma.future_time, future_time); + + helper.next_block(50_000); + + let amp_gamma = helper.query_amp_gamma().unwrap(); + assert_eq!(dec_to_f64(amp_gamma.amp), target_amp); + assert_eq!(dec_to_f64(amp_gamma.gamma), target_gamma); + assert_eq!(amp_gamma.future_time, future_time); + + // change values back + let future_time = helper.app.block_info().time.seconds() + 100_000; + let action = ConcentratedPoolUpdateParams::Promote(PromoteParams { + next_amp: f64_to_dec(40f64), + next_gamma: f64_to_dec(0.000099), + future_time, + }); + helper.update_config(&owner, &action).unwrap(); + + helper.next_block(50_000); + + let amp_gamma = helper.query_amp_gamma().unwrap(); + assert_eq!(dec_to_f64(amp_gamma.amp), 42f64); + assert_eq!(dec_to_f64(amp_gamma.gamma), 0.0000945); + assert_eq!(amp_gamma.future_time, future_time); + + // stop changing amp and gamma thus fixing current values + let action = ConcentratedPoolUpdateParams::StopChangingAmpGamma {}; + helper.update_config(&owner, &action).unwrap(); + let amp_gamma = helper.query_amp_gamma().unwrap(); + let last_change_time = helper.app.block_info().time.seconds(); + assert_eq!(amp_gamma.future_time, last_change_time); + + helper.next_block(50_000); + + let amp_gamma = helper.query_amp_gamma().unwrap(); + assert_eq!(dec_to_f64(amp_gamma.amp), 42f64); + assert_eq!(dec_to_f64(amp_gamma.gamma), 0.0000945); + assert_eq!(amp_gamma.future_time, last_change_time); +} + +#[test] +fn check_prices() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("uusd"), TestCoin::native("usdx")]; + + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); + helper.next_block(50_000); + + let check_prices = |helper: &Helper| { + let prices = helper.query_prices().unwrap(); + + test_coins + .iter() + .cartesian_product(test_coins.iter()) + .filter(|(a, b)| a != b) + .for_each(|(from_coin, to_coin)| { + let price = prices + .cumulative_prices + .iter() + .filter(|(from, to, _)| { + from.eq(&helper.assets[from_coin]) && to.eq(&helper.assets[to_coin]) + }) + .collect::>(); + assert_eq!(price.len(), 1); + assert!(!price[0].2.is_zero()); + }); + }; + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_000_000000u128), + helper.assets[&test_coins[1]].with_balance(100_000_000_000000u128), + ]; + helper.give_me_money(&assets, &owner); + helper.provide_liquidity(&owner, &assets).unwrap(); + check_prices(&helper); + + helper.next_block(1000); + + let user1 = Addr::unchecked("user1"); + let offer_asset = helper.assets[&test_coins[0]].with_balance(1000_000000u128); + helper.give_me_money(&[offer_asset.clone()], &user1); + + helper.swap(&user1, &offer_asset, None).unwrap(); + check_prices(&helper); + + helper.next_block(86400); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000000u128), + helper.assets[&test_coins[1]].with_balance(100_000000u128), + ]; + helper.give_me_money(&assets, &user1); + + helper.provide_liquidity(&user1, &assets).unwrap(); + check_prices(&helper); + + helper.next_block(14 * 86400); + + let offer_asset = helper.assets[&test_coins[1]].with_balance(10_000_000000u128); + helper.give_me_money(&[offer_asset.clone()], &user1); + helper.swap(&user1, &offer_asset, None).unwrap(); + check_prices(&helper); +} + +#[test] +fn update_owner() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("uusd")]; + + let mut helper = Helper::new(&owner, test_coins, common_pcl_params()).unwrap(); + + let new_owner = String::from("new_owner"); + + // New owner + let msg = ExecuteMsg::ProposeNewOwner { + owner: new_owner.clone(), + expires_in: 100, // seconds + }; + + // Unauthorized check + let err = helper + .app + .execute_contract( + Addr::unchecked("not_owner"), + helper.pair_addr.clone(), + &msg, + &[], + ) + .unwrap_err(); + assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); + + // Claim before proposal + let err = helper + .app + .execute_contract( + Addr::unchecked(new_owner.clone()), + helper.pair_addr.clone(), + &ExecuteMsg::ClaimOwnership {}, + &[], + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Ownership proposal not found" + ); + + // Propose new owner + helper + .app + .execute_contract( + Addr::unchecked(&helper.owner), + helper.pair_addr.clone(), + &msg, + &[], + ) + .unwrap(); + + // Claim from invalid addr + let err = helper + .app + .execute_contract( + Addr::unchecked("invalid_addr"), + helper.pair_addr.clone(), + &ExecuteMsg::ClaimOwnership {}, + &[], + ) + .unwrap_err(); + assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); + + // Claim ownership + helper + .app + .execute_contract( + Addr::unchecked(new_owner.clone()), + helper.pair_addr.clone(), + &ExecuteMsg::ClaimOwnership {}, + &[], + ) + .unwrap(); + + let config = helper.query_config().unwrap(); + assert_eq!(config.owner.unwrap().to_string(), new_owner) +} + +#[test] +fn check_orderbook_integration() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("uusd"), TestCoin::native("untrn")]; + + let params = ConcentratedPoolParams { + amp: f64_to_dec(10f64), + price_scale: Decimal::from_ratio(2u8, 1u8), + ..common_pcl_params() + }; + + let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(1_000_000_000000u128), + helper.assets[&test_coins[1]].with_balance(500_000_000000u128), + ]; + helper.give_me_money(&assets, &owner); + helper.provide_liquidity(&owner, &assets).unwrap(); + + helper + .app + .execute_contract( + owner.clone(), + helper.pair_addr.clone(), + &ExecuteMsg::Custom(DualityPairMsg::UpdateOrderbookConfig( + UpdateDualityOrderbook { + enable: Some(true), + executor: None, + remove_executor: false, + orders_number: None, + min_asset_0_order_size: None, + min_asset_1_order_size: None, + liquidity_percent: None, + }, + )), + &[], + ) + .unwrap(); + + let err = helper + .app + .execute_contract( + owner, + helper.pair_addr.clone(), + &ExecuteMsg::Custom(DualityPairMsg::SyncOrderbook {}), + &[], + ) + .unwrap_err(); + + assert_eq!( + ContractError::OrderbookError(OrderbookError::NoNeedToSync {}), + err.downcast().unwrap() + ); + + let user = Addr::unchecked("user"); + let offer_asset = helper.assets[&test_coins[0]].with_balance(100_000000u128); + helper.give_me_money(&[offer_asset.clone()], &user); + helper.swap(&user, &offer_asset, None).unwrap(); + + let ob_config = helper.query_ob_config().unwrap(); + + assert_eq!(ob_config.orders.len() as u8, ob_config.orders_number * 2) +} + +#[ignore] +#[test] +fn check_deactivate_orderbook() { + unimplemented!(); +} + +#[test] +fn provide_withdraw_provide() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("uusd"), TestCoin::native("untrn")]; + + let params = ConcentratedPoolParams { + amp: f64_to_dec(10f64), + price_scale: Decimal::from_ratio(10u8, 1u8), + ..common_pcl_params() + }; + + let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(10_938039u128), + helper.assets[&test_coins[1]].with_balance(1_093804u128), + ]; + helper.give_me_money(&assets, &owner); + helper.provide_liquidity(&owner, &assets).unwrap(); + helper.next_block(90); + helper.give_me_money(&assets, &owner); + helper.provide_liquidity(&owner, &assets).unwrap(); + + helper.next_block(90); + let uusd = helper.assets[&test_coins[0]].with_balance(5_000000u128); + helper.give_me_money(&[uusd.clone()], &owner); + helper.swap(&owner, &uusd, Some(f64_to_dec(0.5))).unwrap(); + + helper.next_block(600); + // Withdraw all + let lp_amount = helper.native_balance(&helper.lp_token, &owner); + helper + .withdraw_liquidity(&owner, lp_amount, vec![]) + .unwrap(); + + // Provide again + helper + .provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.5))) + .unwrap(); +} + +#[test] +fn provide_withdraw_slippage() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("uusd"), TestCoin::native("untrn")]; + + let params = ConcentratedPoolParams { + amp: f64_to_dec(10f64), + price_scale: Decimal::from_ratio(10u8, 1u8), + ..common_pcl_params() + }; + + let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + + // Fully balanced provide + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(10_000000u128), + helper.assets[&test_coins[1]].with_balance(1_000000u128), + ]; + helper.give_me_money(&assets, &owner); + helper + .provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.02))) + .unwrap(); + + // Imbalanced provide. Slippage is more than 2% while we enforce 2% max slippage + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(5_000000u128), + helper.assets[&test_coins[1]].with_balance(1_000000u128), + ]; + helper.give_me_money(&assets, &owner); + let err = helper + .provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.02))) + .unwrap_err(); + assert_eq!( + ContractError::PclError(PclError::MaxSpreadAssertion {}), + err.downcast().unwrap() + ); + // With 3% slippage it should work + helper + .provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.03))) + .unwrap(); + + // Provide with a huge imbalance. Slippage is ~42.2% + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(1000_000000u128), + helper.assets[&test_coins[1]].with_balance(1000_000000u128), + ]; + helper.give_me_money(&assets, &owner); + let err = helper + .provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.02))) + .unwrap_err(); + assert_eq!( + ContractError::PclError(PclError::MaxSpreadAssertion {}), + err.downcast().unwrap(), + ); + helper + .provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.5))) + .unwrap(); +} + +#[test] +fn check_small_trades() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("uusd"), TestCoin::native("uluna")]; + + let params = ConcentratedPoolParams { + price_scale: f64_to_dec(4.360000915600192), + ..common_pcl_params() + }; + + let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + + helper.give_me_money( + &[ + helper.assets[&test_coins[0]].with_balance(u128::MAX / 2), + helper.assets[&test_coins[1]].with_balance(u128::MAX / 2), + ], + &owner, + ); + + // Fully balanced but small provide + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(8_000000u128), + helper.assets[&test_coins[1]].with_balance(1_834862u128), + ]; + helper.provide_liquidity(&owner, &assets).unwrap(); + + // Trying to mess the last price with lowest possible swap + for _ in 0..1000 { + helper.next_block(30); + let offer_asset = helper.assets[&test_coins[1]].with_balance(1u8); + helper + .swap_full_params(&owner, &offer_asset, None, Some(Decimal::MAX)) + .unwrap(); + } + + // Check that after price scale adjustments (even they are small) internal value is still nearly balanced + let config = helper.query_config().unwrap(); + let pool = helper + .query_pool() + .unwrap() + .assets + .into_iter() + .map(|asset| asset.amount.to_decimal256(6u8).unwrap()) + .collect_vec(); + + let ixs = [pool[0], pool[1] * config.pool_state.price_state.price_scale]; + let relative_diff = ixs[0].abs_diff(ixs[1]) / max(&ixs).unwrap(); + + assert!( + relative_diff < Decimal256::percent(3), + "Internal PCL value is off. Relative_diff: {}", + relative_diff + ); + + // Trying to mess the last price with lowest possible provide + for _ in 0..1000 { + helper.next_block(30); + let assets = vec![helper.assets[&test_coins[1]].with_balance(1u8)]; + helper + .provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.5))) + .unwrap(); + } + + // Check that after price scale adjustments (even they are small) internal value is still nearly balanced + let config = helper.query_config().unwrap(); + let pool = helper + .query_pool() + .unwrap() + .assets + .into_iter() + .map(|asset| asset.amount.to_decimal256(6u8).unwrap()) + .collect_vec(); + + let ixs = [pool[0], pool[1] * config.pool_state.price_state.price_scale]; + let relative_diff = ixs[0].abs_diff(ixs[1]) / max(&ixs).unwrap(); + + assert!( + relative_diff < Decimal256::percent(3), + "Internal PCL value is off. Relative_diff: {}", + relative_diff + ); +} diff --git a/contracts/pair_stable/src/contract.rs b/contracts/pair_stable/src/contract.rs index 2ff1c84e5..332edbce9 100644 --- a/contracts/pair_stable/src/contract.rs +++ b/contracts/pair_stable/src/contract.rs @@ -279,6 +279,7 @@ pub fn execute( assets, min_assets_to_receive, } => withdraw_liquidity(deps, env, info, assets, min_assets_to_receive), + _ => Err(ContractError::NotSupported {}), } } diff --git a/contracts/pair_stable/src/error.rs b/contracts/pair_stable/src/error.rs index 315fb4839..d397cbabf 100644 --- a/contracts/pair_stable/src/error.rs +++ b/contracts/pair_stable/src/error.rs @@ -114,6 +114,9 @@ pub enum ContractError { #[error("Wrong asset length: expected {expected}, actual {actual}")] WrongAssetLength { expected: usize, actual: usize }, + + #[error("Operation is not supported")] + NotSupported {}, } impl From for ContractError { diff --git a/contracts/pair_transmuter/tests/helper.rs b/contracts/pair_transmuter/tests/helper.rs index a569eba19..0ea13f9b6 100644 --- a/contracts/pair_transmuter/tests/helper.rs +++ b/contracts/pair_transmuter/tests/helper.rs @@ -128,6 +128,7 @@ impl Helper { owner, )) } + TestCoin::NativePrecise(..) => unimplemented!(), }; (coin, asset_info) }) diff --git a/contracts/periphery/astro_converter_neutron/Cargo.toml b/contracts/periphery/astro_converter_neutron/Cargo.toml index 3ea2e3c7e..8dfef1179 100644 --- a/contracts/periphery/astro_converter_neutron/Cargo.toml +++ b/contracts/periphery/astro_converter_neutron/Cargo.toml @@ -15,7 +15,7 @@ crate-type = ["cdylib", "rlib"] library = [] [dependencies] -neutron-sdk = "0.8.0" +neutron-sdk = "0.9.0" astroport = "4" astro-token-converter = { path = "../astro_converter", version = "1.0", features = ["library"] } cosmwasm-std = "1.5" diff --git a/packages/astroport/Cargo.toml b/packages/astroport/Cargo.toml index 216332ae6..d35dbe20a 100644 --- a/packages/astroport/Cargo.toml +++ b/packages/astroport/Cargo.toml @@ -16,6 +16,7 @@ homepage = "https://astroport.fi" backtraces = ["cosmwasm-std/backtraces"] injective = ["injective-math", "thiserror"] sei = [] +duality = [] [dependencies] cw20 = "1.1" diff --git a/packages/astroport/src/lib.rs b/packages/astroport/src/lib.rs index 15c902eef..bd2dbca75 100644 --- a/packages/astroport/src/lib.rs +++ b/packages/astroport/src/lib.rs @@ -33,6 +33,8 @@ mod mock_querier; pub mod astro_converter; pub mod incentives; +#[cfg(feature = "duality")] +pub mod pair_concentrated_duality; #[cfg(test)] mod testing; diff --git a/packages/astroport/src/pair.rs b/packages/astroport/src/pair.rs index 6ebbfcc9d..2a036c923 100644 --- a/packages/astroport/src/pair.rs +++ b/packages/astroport/src/pair.rs @@ -4,7 +4,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use crate::asset::{Asset, AssetInfo, PairInfo}; use crate::factory::PairType; -use cosmwasm_std::{Addr, Binary, Decimal, Decimal256, StdError, Uint128, Uint64}; +use cosmwasm_std::{Addr, Binary, Decimal, Decimal256, Empty, StdError, Uint128, Uint64}; use cw20::Cw20ReceiveMsg; /// The default swap slippage @@ -38,7 +38,7 @@ pub struct InstantiateMsg { /// This structure describes the execute messages available in the contract. #[cw_serde] -pub enum ExecuteMsg { +pub enum ExecuteMsgExt { /// Receives a message of type [`Cw20ReceiveMsg`] Receive(Cw20ReceiveMsg), /// ProvideLiquidity allows someone to provide liquidity in the pool @@ -81,8 +81,12 @@ pub enum ExecuteMsg { DropOwnershipProposal {}, /// Used to claim contract ownership. ClaimOwnership {}, + /// Custom execute endpoints for extended pool implementations + Custom(C), } +pub type ExecuteMsg = ExecuteMsgExt; + /// This structure describes a CW20 hook message. #[cw_serde] pub enum Cw20HookMsg { diff --git a/packages/astroport/src/pair_concentrated_duality.rs b/packages/astroport/src/pair_concentrated_duality.rs new file mode 100644 index 000000000..ea6d37c1b --- /dev/null +++ b/packages/astroport/src/pair_concentrated_duality.rs @@ -0,0 +1,75 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Decimal, StdError, Uint128}; + +use crate::pair_concentrated::ConcentratedPoolParams; + +#[cw_serde] +pub struct OrderbookConfig { + /// Determines whether the orderbook is enabled + pub enable: bool, + /// The address of the orderbook sync executor. If None, then the sync is permissionless. + pub executor: Option, + /// Number of orders on each side of the orderbook + pub orders_number: u8, + /// Minimum order size for asset 0 + pub min_asset_0_order_size: Uint128, + /// Minimum order size for asset 1 + pub min_asset_1_order_size: Uint128, + /// Percent of liquidity to be deployed to the orderbook + pub liquidity_percent: Decimal, +} + +#[cw_serde] +pub struct UpdateDualityOrderbook { + /// Determines whether the orderbook is enabled + pub enable: Option, + /// The address of the orderbook sync executor + pub executor: Option, + /// Determines whether the executor should be removed. + /// If removed, then sync endpoint becomes permissionless + #[serde(default)] + pub remove_executor: bool, + /// Number of orders on each side of the orderbook + pub orders_number: Option, + /// Minimum order size for asset 0 + pub min_asset_0_order_size: Option, + /// Minimum order size for asset 1 + pub min_asset_1_order_size: Option, + /// Percent of liquidity to be deployed to the orderbook + pub liquidity_percent: Option, +} + +/// This structure holds concentrated pool parameters along with orderbook params. +#[cw_serde] +pub struct ConcentratedDualityParams { + pub main_params: ConcentratedPoolParams, + pub orderbook_config: OrderbookConfig, +} + +#[cw_serde] +pub enum DualityPairMsg { + SyncOrderbook {}, + UpdateOrderbookConfig(UpdateDualityOrderbook), +} + +/// A `reply` call code ID used for sub-messages. +#[cw_serde] +pub enum ReplyIds { + CreateDenom = 1, + PostLimitOrderCb = 2, +} + +impl TryFrom for ReplyIds { + type Error = StdError; + + fn try_from(value: u64) -> Result { + match value { + 1 => Ok(ReplyIds::CreateDenom), + 2 => Ok(ReplyIds::PostLimitOrderCb), + _ => Err(StdError::ParseErr { + target_type: "ReplyIds".to_string(), + msg: "Failed to parse reply".to_string(), + }), + } + } +} diff --git a/packages/astroport_test/Cargo.toml b/packages/astroport_test/Cargo.toml index 08cfbe9cf..7d14cb46e 100644 --- a/packages/astroport_test/Cargo.toml +++ b/packages/astroport_test/Cargo.toml @@ -21,12 +21,17 @@ cosmwasm_1_4 = ["cosmwasm_1_3", "cosmwasm-std/cosmwasm_1_4", "cw-multi-test/cosm [dependencies] astroport = { path = "../astroport" } +astroport-factory = { path = "../../contracts/factory" } cosmwasm-schema = "1.2.5" cosmwasm-std = "1.2.5" cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch = "feat/bank_with_send_hooks_1_0", features = ["cosmwasm_1_1"] } serde = "1.0" schemars = "0.8.1" anyhow = "1.0" +cw20-base = "1.1" itertools = { workspace = true } cw-utils = { workspace = true } -cw-storage-plus = { workspace = true } \ No newline at end of file +cw-storage-plus = { workspace = true } +neutron-sdk = { package = "astroport-neutron-sdk", git = "https://github.com/epanchee/neutron-sdk", branch = "backport/cosmwasm_v1" } +prost = "0.12" +sha2 = "0.10.8" \ No newline at end of file diff --git a/packages/astroport_test/src/coins.rs b/packages/astroport_test/src/coins.rs index 9b371bde9..487a24355 100644 --- a/packages/astroport_test/src/coins.rs +++ b/packages/astroport_test/src/coins.rs @@ -3,12 +3,13 @@ pub enum TestCoin { Cw20(String), Cw20Precise(String, u8), Native(String), + NativePrecise(String, u8), } impl TestCoin { pub fn denom(&self) -> Option { match self { - TestCoin::Native(denom) => Some(denom.clone()), + TestCoin::Native(denom) | TestCoin::NativePrecise(denom, ..) => Some(denom.clone()), _ => None, } } @@ -21,10 +22,22 @@ impl TestCoin { } } + pub fn decimals(&self) -> u8 { + match self { + TestCoin::NativePrecise(_, precision) => *precision, + TestCoin::Cw20Precise(_, precision) => *precision, + _ => 6, + } + } + pub fn native(denom: &str) -> Self { Self::Native(denom.to_string()) } + pub fn native_precise(denom: &str, precision: u8) -> Self { + Self::NativePrecise(denom.to_string(), precision) + } + pub fn cw20(name: &str) -> Self { Self::Cw20(name.to_string()) } diff --git a/packages/astroport_test/src/lib.rs b/packages/astroport_test/src/lib.rs index c3cadb9df..9e21117df 100644 --- a/packages/astroport_test/src/lib.rs +++ b/packages/astroport_test/src/lib.rs @@ -1,5 +1,6 @@ #![cfg(not(tarpaulin_include))] +pub use cw20_base; pub use cw_multi_test; pub mod coins; diff --git a/packages/astroport_test/src/modules/mod.rs b/packages/astroport_test/src/modules/mod.rs index 570f7f580..c1780700d 100644 --- a/packages/astroport_test/src/modules/mod.rs +++ b/packages/astroport_test/src/modules/mod.rs @@ -1 +1,2 @@ +pub mod neutron_stargate; pub mod stargate; diff --git a/packages/astroport_test/src/modules/neutron_stargate.rs b/packages/astroport_test/src/modules/neutron_stargate.rs new file mode 100644 index 000000000..9a8277496 --- /dev/null +++ b/packages/astroport_test/src/modules/neutron_stargate.rs @@ -0,0 +1,207 @@ +use std::cell::RefCell; +use std::collections::HashMap; + +use anyhow::{Ok, Result as AnyResult}; +use cosmwasm_schema::serde::de::DeserializeOwned; +use cosmwasm_std::{ + coin, coins, to_json_binary, Addr, Api, BankMsg, Binary, BlockInfo, CustomMsg, CustomQuery, + Empty, Querier, Storage, SubMsgResponse, +}; +use cw_multi_test::{ + AppResponse, BankSudo, CosmosRouter, Module, Stargate, StargateMsg, StargateQuery, SudoMsg, +}; +use itertools::Itertools; +use neutron_sdk::proto_types::neutron::dex::{ + LimitOrderTrancheUser, MsgCancelLimitOrderResponse, MsgPlaceLimitOrder, + QueryAllLimitOrderTrancheUserByAddressRequest, QueryAllLimitOrderTrancheUserByAddressResponse, + QuerySimulateCancelLimitOrderRequest, QuerySimulateCancelLimitOrderResponse, +}; +use prost::Message; +use sha2::Digest; + +use astroport::token_factory::{ + MsgBurn, MsgCreateDenom, MsgCreateDenomResponse, MsgMint, MsgSetBeforeSendHook, +}; + +#[derive(Default)] +pub struct NeutronStargate { + // user -> tranche_key -> limit_order + orders: RefCell>>, +} + +impl Stargate for NeutronStargate {} + +impl Module for NeutronStargate { + type ExecT = StargateMsg; + type QueryT = StargateQuery; + type SudoT = Empty; + + fn execute( + &self, + api: &dyn Api, + storage: &mut dyn Storage, + router: &dyn CosmosRouter, + block: &BlockInfo, + sender: Addr, + msg: Self::ExecT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + let StargateMsg { + type_url, value, .. + } = msg; + + match type_url.as_str() { + MsgCreateDenom::TYPE_URL => { + let tf_msg: MsgCreateDenom = value.try_into()?; + let submsg_response = SubMsgResponse { + events: vec![], + data: Some( + MsgCreateDenomResponse { + new_token_denom: format!("factory/{sender}/{}", tf_msg.subdenom), + } + .into(), + ), + }; + Ok(submsg_response.into()) + } + MsgMint::TYPE_URL => { + let tf_msg: MsgMint = value.try_into()?; + let mint_coins = tf_msg + .amount + .expect("Empty amount in tokenfactory MsgMint!"); + let to_address = tf_msg.mint_to_address.to_string(); + let bank_sudo = BankSudo::Mint { + to_address, + amount: coins(mint_coins.amount.parse()?, mint_coins.denom), + }; + router.sudo(api, storage, block, bank_sudo.into()) + } + MsgBurn::TYPE_URL => { + let tf_msg: MsgBurn = value.try_into()?; + let burn_coins = tf_msg + .amount + .expect("Empty amount in tokenfactory MsgBurn!"); + let burn_msg = BankMsg::Burn { + amount: coins(burn_coins.amount.parse()?, burn_coins.denom), + }; + router.execute( + api, + storage, + block, + Addr::unchecked(sender), + burn_msg.into(), + ) + } + MsgSetBeforeSendHook::TYPE_URL => { + let before_hook_msg: MsgSetBeforeSendHook = value.try_into()?; + let msg = BankSudo::SetHook { + contract_addr: before_hook_msg.cosmwasm_address, + denom: before_hook_msg.denom, + }; + router.sudo(api, storage, block, SudoMsg::Bank(msg)) + } + MsgPlaceLimitOrder::TYPE_URL => { + let tranche_key = format!("{:x}", sha2::Sha256::digest(&value)); + + self.orders + .borrow_mut() + .entry(sender.to_string()) + .or_insert_with(HashMap::new) + .insert(tranche_key, value.try_into()?); + Ok(AppResponse::default()) + } + _ => Err(anyhow::anyhow!( + "Unexpected exec msg {type_url} from {sender:?}", + )), + } + } + + fn query( + &self, + _api: &dyn Api, + _storage: &dyn Storage, + _querier: &dyn Querier, + _block: &BlockInfo, + request: Self::QueryT, + ) -> AnyResult { + match request.path.as_str() { + "/neutron.dex.Query/LimitOrderTrancheUserAllByAddress" => { + let request = + QueryAllLimitOrderTrancheUserByAddressRequest::decode(request.data.as_slice()) + .unwrap(); + + Ok(to_json_binary( + &QueryAllLimitOrderTrancheUserByAddressResponse { + limit_orders: self + .orders + .borrow() + .get(&request.address) + .map(|m| { + m.iter() + .map(|(tranche_key, order)| LimitOrderTrancheUser { + trade_pair_id: None, + tick_index_taker_to_maker: 0, + tranche_key: tranche_key.clone(), + address: order.receiver.clone(), + shares_owned: "".to_string(), + shares_withdrawn: "".to_string(), + shares_cancelled: "".to_string(), + order_type: order.order_type, + }) + .collect_vec() + }) + .unwrap_or_default(), + pagination: None, + }, + )?) + } + "/neutron.dex.Query/SimulateCancelLimitOrder" => { + let request = QuerySimulateCancelLimitOrderRequest::decode(request.data.as_slice()) + .unwrap() + .msg + .unwrap(); + + let order = self + .orders + .borrow() + .get(&request.creator) + .unwrap() + .get(&request.tranche_key) + .cloned() + .unwrap(); + let resp = Some(MsgCancelLimitOrderResponse { + taker_coin_out: None, + maker_coin_out: Some( + coin(order.amount_in.parse().unwrap(), &order.token_in).into(), + ), + }); + + Ok(to_json_binary(&QuerySimulateCancelLimitOrderResponse { + resp, + })?) + } + _ => Err(anyhow::anyhow!( + "Unexpected stargate query request {}", + request.path + )), + } + } + + fn sudo( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + _msg: Self::SudoT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + unimplemented!("Sudo not implemented") + } +} From 9e741734e23856da6a3b6be65c8c2e713b51f84f Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:49:30 +0300 Subject: [PATCH 02/24] WIP: fix test-tube tests --- .../tests/common/astroport_wrapper.rs | 11 ++++++-- .../tests/pcl_duality_e2e.rs | 27 ++++++++++++++++--- .../tests/pcl_duality_integration.rs | 8 ++---- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs b/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs index dbe894ed9..7edb08ef3 100644 --- a/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs +++ b/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs @@ -182,7 +182,10 @@ impl<'a> AstroportHelper<'a> { Ok(Self { helper, owner: signer, - assets: HashMap::new(), + assets: asset_infos_vec + .into_iter() + .map(|(test_coin, asset_info)| (test_coin, asset_info)) + .collect(), maker: maker_addr, factory: Addr::unchecked(factory), pair_addr: contract_addr, @@ -206,7 +209,11 @@ impl<'a> AstroportHelper<'a> { assets: &[Asset], slippage_tolerance: Option, ) -> AnyResult<()> { - let funds = assets.iter().map(|a| a.as_coin().unwrap()).collect_vec(); + let funds = assets + .iter() + .map(|a| a.as_coin().unwrap()) + .sorted_by(|a, b| a.denom.cmp(&b.denom)) + .collect_vec(); let msg = ExecuteMsg::ProvideLiquidity { assets: assets.to_vec(), diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs index 0814f7107..59e28476d 100644 --- a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs @@ -2,7 +2,8 @@ #![cfg(feature = "test-tube")] #![allow(dead_code)] -use cosmwasm_std::{Decimal, Uint128}; +use astroport::asset::AssetInfoExt; +use cosmwasm_std::{coin, Decimal, Uint128}; use neutron_test_tube::{Account, NeutronTestApp}; use astroport::pair_concentrated_duality::OrderbookConfig; @@ -19,9 +20,10 @@ fn init_on_duality() { let app = NeutronTestApp::new(); let neutron = TestAppWrapper::bootstrap(&app).unwrap(); let owner = neutron.signer.address(); - let _astroport = AstroportHelper::new( + + let astroport = AstroportHelper::new( neutron, - test_coins, + test_coins.clone(), common_pcl_params(), OrderbookConfig { enable: true, @@ -31,5 +33,22 @@ fn init_on_duality() { min_asset_0_order_size: Uint128::from(1_000u128), min_asset_1_order_size: Uint128::from(1_000u128), }, - ); + ) + .unwrap(); + + let user = astroport + .helper + .app + .init_account(&[coin(2_000_000, "untrn"), coin(1_000_000, "astro")]) + .unwrap(); + + astroport + .provide_liquidity( + &user, + &[ + astroport.assets[&test_coins[0]].with_balance(1_000_000u128), + astroport.assets[&test_coins[1]].with_balance(1_000_000u128), + ], + ) + .unwrap(); } diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs index 82aea0633..cc7a9d21b 100644 --- a/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs @@ -920,13 +920,9 @@ fn check_orderbook_integration() { let ob_config = helper.query_ob_config().unwrap(); - assert_eq!(ob_config.orders.len() as u8, ob_config.orders_number * 2) -} + assert_eq!(ob_config.orders.len() as u8, ob_config.orders_number * 2); -#[ignore] -#[test] -fn check_deactivate_orderbook() { - unimplemented!(); + // TODO: test orderbook disabling } #[test] From e70930a480d065cde2b621d6166d2bf8e1935583 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:24:04 +0300 Subject: [PATCH 03/24] bump neutron test-tube and related packages --- Cargo.lock | 45 +++++++++++++++---- .../pair_concentrated_duality/Cargo.toml | 5 ++- .../src/orderbook/state.rs | 4 +- .../src/orderbook/utils.rs | 4 +- packages/astroport_test/Cargo.toml | 1 + .../src/modules/neutron_stargate.rs | 2 +- 6 files changed, 46 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f77dd4f0..d4647ecfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,14 +333,14 @@ dependencies = [ [[package]] name = "astroport-neutron-sdk" version = "0.11.0-cosmwasm-v1" -source = "git+https://github.com/epanchee/neutron-sdk?branch=backport/cosmwasm_v1#2c94553f36230ecaf5ab8f4c5ce856fa64d8cc59" +source = "git+https://github.com/epanchee/neutron-sdk?branch=backport/cosmwasm_v1#e97a03aa4392f4aa08666c99090d4abb14339abb" dependencies = [ "bech32 0.9.1", "chrono", "cosmos-sdk-proto 0.20.0", "cosmwasm-schema", "cosmwasm-std", - "neutron-std-derive", + "neutron-std", "prost 0.12.6", "prost-types 0.12.6", "protobuf 3.3.0", @@ -458,7 +458,8 @@ dependencies = [ "cw2 1.1.2", "derivative", "itertools 0.12.1", - "neutron-test-tube 4.0.1", + "neutron-std", + "neutron-test-tube 5.0.0", "thiserror", ] @@ -626,6 +627,7 @@ dependencies = [ "cw-utils 1.0.3", "cw20-base 1.1.2", "itertools 0.12.1", + "neutron-std", "prost 0.12.6", "schemars", "serde", @@ -838,7 +840,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -2365,10 +2367,34 @@ dependencies = [ "thiserror", ] +[[package]] +name = "neutron-std" +version = "5.0.0" +source = "git+https://github.com/epanchee/neutron-std?branch=backport/cosmwasm_v1#b07f4f11dff14f4fe8a46819d43ece2aee30c481" +dependencies = [ + "bech32 0.9.1", + "chrono", + "cosmos-sdk-proto 0.20.0", + "cosmwasm-schema", + "cosmwasm-std", + "neutron-std-derive", + "prost 0.12.6", + "prost-types 0.12.6", + "protobuf 3.3.0", + "schemars", + "serde", + "serde-cw-value", + "serde-json-wasm 1.0.1", + "serde_json", + "speedate", + "tendermint-proto 0.34.1", + "thiserror", +] + [[package]] name = "neutron-std-derive" version = "0.20.1" -source = "git+https://github.com/epanchee/neutron-sdk?branch=backport/cosmwasm_v1#2c94553f36230ecaf5ab8f4c5ce856fa64d8cc59" +source = "git+https://github.com/epanchee/neutron-std?branch=backport/cosmwasm_v1#b07f4f11dff14f4fe8a46819d43ece2aee30c481" dependencies = [ "itertools 0.10.5", "proc-macro2", @@ -2396,8 +2422,8 @@ dependencies = [ [[package]] name = "neutron-test-tube" -version = "4.0.1" -source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/cosmwasm-v1#518172e3d0fc52f16b291864bc2bf0a4d9c56eb0" +version = "5.0.0" +source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/neutron-v5-cosmwasm-v1#f982e491f798c8dc8867c414ee86c58c2b918bb1" dependencies = [ "astroport-neutron-sdk", "base64", @@ -2406,6 +2432,7 @@ dependencies = [ "cosmrs", "cosmwasm-std", "hex", + "neutron-std", "prost 0.12.6", "serde", "serde_json", @@ -2723,7 +2750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.68", @@ -3767,7 +3794,7 @@ dependencies = [ [[package]] name = "test-tube-ntrn" version = "0.1.0" -source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/cosmwasm-v1#518172e3d0fc52f16b291864bc2bf0a4d9c56eb0" +source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/neutron-v5-cosmwasm-v1#f982e491f798c8dc8867c414ee86c58c2b918bb1" dependencies = [ "base64", "cosmos-sdk-proto 0.20.0", diff --git a/contracts/pair_concentrated_duality/Cargo.toml b/contracts/pair_concentrated_duality/Cargo.toml index 182f662a1..e95b24fdf 100644 --- a/contracts/pair_concentrated_duality/Cargo.toml +++ b/contracts/pair_concentrated_duality/Cargo.toml @@ -35,12 +35,13 @@ itertools = { workspace = true } cw-utils = { workspace = true } cw2 = { workspace = true } -#neutron-sdk = { package = "neutron-sdk", git = "https://github.com/neutron-org/neutron-sdk", branch = "feat/remove-stargate" } neutron-sdk = { package = "astroport-neutron-sdk", git = "https://github.com/epanchee/neutron-sdk", branch = "backport/cosmwasm_v1" } +neutron-std = { git = "https://github.com/epanchee/neutron-std", branch = "backport/cosmwasm_v1" } + astroport = { path = "../../packages/astroport", version = "5.5", features = ["duality"] } astroport-pcl-common = { path = "../../packages/astroport_pcl_common", version = "2" } -neutron-test-tube = { git = "https://github.com/epanchee/neutron-test-tube-new", optional = true, branch = "feat/cosmwasm-v1" } +neutron-test-tube = { git = "https://github.com/epanchee/neutron-test-tube-new", optional = true, branch = "feat/neutron-v5-cosmwasm-v1", version = "5.0.0" } [dev-dependencies] astroport-native-coin-registry = "1.1" diff --git a/contracts/pair_concentrated_duality/src/orderbook/state.rs b/contracts/pair_concentrated_duality/src/orderbook/state.rs index 1db4d3b5e..16e61cb6b 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/state.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/state.rs @@ -7,8 +7,8 @@ use cosmwasm_std::{ }; use cw_storage_plus::Item; use itertools::Itertools; -use neutron_sdk::proto_types::cosmos::base::query::v1beta1::PageRequest; -use neutron_sdk::proto_types::neutron::dex::{ +use neutron_std::types::cosmos::base::query::v1beta1::PageRequest; +use neutron_std::types::neutron::dex::{ DexQuerier, MsgCancelLimitOrder, MsgCancelLimitOrderResponse, MsgWithdrawFilledLimitOrder, }; diff --git a/contracts/pair_concentrated_duality/src/orderbook/utils.rs b/contracts/pair_concentrated_duality/src/orderbook/utils.rs index 1f3e09ee5..e6f894051 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/utils.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/utils.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use cosmwasm_std::{Addr, CosmosMsg, Decimal256, StdResult}; -use neutron_sdk::proto_types::neutron::dex::MsgPlaceLimitOrder; +use neutron_std::types::neutron::dex::MsgPlaceLimitOrder; use astroport::asset::{AssetInfo, Decimal256Ext}; use astroport_pcl_common::{ @@ -131,6 +131,7 @@ impl SpotOrdersFactory { token_out: self.denoms[0].clone(), limit_sell_price, tick_index_in_to_out: 0i64, + min_average_sell_price: "".to_string(), } .into() } else { @@ -150,6 +151,7 @@ impl SpotOrdersFactory { token_out: self.denoms[1].clone(), limit_sell_price, tick_index_in_to_out: 0i64, + min_average_sell_price: "".to_string(), } .into() } diff --git a/packages/astroport_test/Cargo.toml b/packages/astroport_test/Cargo.toml index 7d14cb46e..5d73fa20d 100644 --- a/packages/astroport_test/Cargo.toml +++ b/packages/astroport_test/Cargo.toml @@ -33,5 +33,6 @@ itertools = { workspace = true } cw-utils = { workspace = true } cw-storage-plus = { workspace = true } neutron-sdk = { package = "astroport-neutron-sdk", git = "https://github.com/epanchee/neutron-sdk", branch = "backport/cosmwasm_v1" } +neutron-std = { git = "https://github.com/epanchee/neutron-std", branch = "backport/cosmwasm_v1" } prost = "0.12" sha2 = "0.10.8" \ No newline at end of file diff --git a/packages/astroport_test/src/modules/neutron_stargate.rs b/packages/astroport_test/src/modules/neutron_stargate.rs index 9a8277496..68eea0868 100644 --- a/packages/astroport_test/src/modules/neutron_stargate.rs +++ b/packages/astroport_test/src/modules/neutron_stargate.rs @@ -11,7 +11,7 @@ use cw_multi_test::{ AppResponse, BankSudo, CosmosRouter, Module, Stargate, StargateMsg, StargateQuery, SudoMsg, }; use itertools::Itertools; -use neutron_sdk::proto_types::neutron::dex::{ +use neutron_std::types::neutron::dex::{ LimitOrderTrancheUser, MsgCancelLimitOrderResponse, MsgPlaceLimitOrder, QueryAllLimitOrderTrancheUserByAddressRequest, QueryAllLimitOrderTrancheUserByAddressResponse, QuerySimulateCancelLimitOrderRequest, QuerySimulateCancelLimitOrderResponse, From 60af0317608c0b218a08d02d7fbe0e7df0a32e97 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:25:40 +0300 Subject: [PATCH 04/24] WIP: adjust code to new types --- Cargo.lock | 14 +-- .../pair_concentrated_duality/Cargo.toml | 3 +- .../pair_concentrated_duality/src/execute.rs | 6 +- .../src/orderbook/execute.rs | 3 +- .../src/orderbook/state.rs | 3 +- .../src/orderbook/utils.rs | 118 +++++++++++------- .../tests/common/helper.rs | 2 +- .../tests/pcl_duality_e2e.rs | 9 +- 8 files changed, 91 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4647ecfa..203fff159 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,7 +448,6 @@ dependencies = [ "astroport 5.5.0", "astroport-factory 1.9.0", "astroport-native-coin-registry 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "astroport-neutron-sdk", "astroport-pcl-common", "astroport-test", "cosmwasm-schema", @@ -459,7 +458,7 @@ dependencies = [ "derivative", "itertools 0.12.1", "neutron-std", - "neutron-test-tube 5.0.0", + "neutron-test-tube 4.2.2-rc", "thiserror", ] @@ -2370,7 +2369,7 @@ dependencies = [ [[package]] name = "neutron-std" version = "5.0.0" -source = "git+https://github.com/epanchee/neutron-std?branch=backport/cosmwasm_v1#b07f4f11dff14f4fe8a46819d43ece2aee30c481" +source = "git+https://github.com/epanchee/neutron-std?branch=backport/cosmwasm_v1#8a97538b5723a9799dd5e6b5a6c4db690bfab07c" dependencies = [ "bech32 0.9.1", "chrono", @@ -2394,7 +2393,7 @@ dependencies = [ [[package]] name = "neutron-std-derive" version = "0.20.1" -source = "git+https://github.com/epanchee/neutron-std?branch=backport/cosmwasm_v1#b07f4f11dff14f4fe8a46819d43ece2aee30c481" +source = "git+https://github.com/epanchee/neutron-std?branch=backport/cosmwasm_v1#8a97538b5723a9799dd5e6b5a6c4db690bfab07c" dependencies = [ "itertools 0.10.5", "proc-macro2", @@ -2422,10 +2421,9 @@ dependencies = [ [[package]] name = "neutron-test-tube" -version = "5.0.0" -source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/neutron-v5-cosmwasm-v1#f982e491f798c8dc8867c414ee86c58c2b918bb1" +version = "4.2.2-rc" +source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/neutron-v5-cosmwasm-v1#ad24013d8de121d4ddb2f1686add5ad36ad220ec" dependencies = [ - "astroport-neutron-sdk", "base64", "bindgen 0.60.1", "cosmos-sdk-proto 0.20.0", @@ -3794,7 +3792,7 @@ dependencies = [ [[package]] name = "test-tube-ntrn" version = "0.1.0" -source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/neutron-v5-cosmwasm-v1#f982e491f798c8dc8867c414ee86c58c2b918bb1" +source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/neutron-v5-cosmwasm-v1#ad24013d8de121d4ddb2f1686add5ad36ad220ec" dependencies = [ "base64", "cosmos-sdk-proto 0.20.0", diff --git a/contracts/pair_concentrated_duality/Cargo.toml b/contracts/pair_concentrated_duality/Cargo.toml index e95b24fdf..79594ccd9 100644 --- a/contracts/pair_concentrated_duality/Cargo.toml +++ b/contracts/pair_concentrated_duality/Cargo.toml @@ -35,13 +35,12 @@ itertools = { workspace = true } cw-utils = { workspace = true } cw2 = { workspace = true } -neutron-sdk = { package = "astroport-neutron-sdk", git = "https://github.com/epanchee/neutron-sdk", branch = "backport/cosmwasm_v1" } neutron-std = { git = "https://github.com/epanchee/neutron-std", branch = "backport/cosmwasm_v1" } astroport = { path = "../../packages/astroport", version = "5.5", features = ["duality"] } astroport-pcl-common = { path = "../../packages/astroport_pcl_common", version = "2" } -neutron-test-tube = { git = "https://github.com/epanchee/neutron-test-tube-new", optional = true, branch = "feat/neutron-v5-cosmwasm-v1", version = "5.0.0" } +neutron-test-tube = { git = "https://github.com/epanchee/neutron-test-tube-new", optional = true, branch = "feat/neutron-v5-cosmwasm-v1", version = "4.2.2-rc" } [dev-dependencies] astroport-native-coin-registry = "1.1" diff --git a/contracts/pair_concentrated_duality/src/execute.rs b/contracts/pair_concentrated_duality/src/execute.rs index 45104a911..5a4384505 100644 --- a/contracts/pair_concentrated_duality/src/execute.rs +++ b/contracts/pair_concentrated_duality/src/execute.rs @@ -268,7 +268,7 @@ pub fn provide_liquidity( .map(|(asset, deposit)| asset.amount + deposit) .collect_vec(); let cancel_msgs = ob_state.cancel_orders(&env.contract.address); - let order_msgs = ob_state.deploy_orders(&env, &config, &balances, &precisions)?; + let order_msgs = ob_state.deploy_orders(&env, &config, &balances, &precisions, deps.api)?; CONFIG.save(deps.storage, &config)?; @@ -365,7 +365,7 @@ fn withdraw_liquidity( let enough_liq_cond = refund_assets[0].amount <= contract_balances[0].amount && refund_assets[1].amount <= contract_balances[1].amount; let order_msgs = if enough_liq_cond { - ob_state.deploy_orders(&env, &config, &xs, &precisions)? + ob_state.deploy_orders(&env, &config, &xs, &precisions, deps.api)? } else { vec![] }; @@ -578,7 +578,7 @@ fn swap( // Reconcile orders let cancel_msgs = ob_state.cancel_orders(&env.contract.address); - let order_msgs = ob_state.deploy_orders(&env, &config, &xs, &precisions)?; + let order_msgs = ob_state.deploy_orders(&env, &config, &xs, &precisions, deps.api)?; CONFIG.save(deps.storage, &config)?; diff --git a/contracts/pair_concentrated_duality/src/orderbook/execute.rs b/contracts/pair_concentrated_duality/src/orderbook/execute.rs index 17713b10b..dfd9804f4 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/execute.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/execute.rs @@ -173,7 +173,8 @@ pub fn sync_pool_with_orderbook( let cancel_msgs = ob_state.cancel_orders(&env.contract.address); let balances = pools.iter().map(|asset| asset.amount).collect_vec(); - let order_msgs = ob_state.deploy_orders(&env, &config, &balances, &precisions)?; + // TODO: remove api + let order_msgs = ob_state.deploy_orders(&env, &config, &balances, &precisions, deps.api)?; let submsgs = ob_state.flatten_msgs_and_add_callback(&[cancel_msgs, order_msgs]); diff --git a/contracts/pair_concentrated_duality/src/orderbook/state.rs b/contracts/pair_concentrated_duality/src/orderbook/state.rs index 16e61cb6b..dc5a5688a 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/state.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/state.rs @@ -356,6 +356,7 @@ impl OrderbookState { config: &Config, balances: &[Decimal256], precisions: &Precisions, + api: &dyn Api, ) -> Result, ContractError> { // Orderbook is disabled. No need to deploy orders. if !self.enabled { @@ -438,7 +439,7 @@ impl OrderbookState { orders_factory.buy(buy_price, buy_amount); } - Ok(orders_factory.collect_spot_orders(&env.contract.address)) + Ok(orders_factory.collect_spot_orders(&env.contract.address, api)) } /// Flatten all messages into one vector and add a callback to the last message only diff --git a/contracts/pair_concentrated_duality/src/orderbook/utils.rs b/contracts/pair_concentrated_duality/src/orderbook/utils.rs index e6f894051..a39ad30cf 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/utils.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/utils.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use cosmwasm_std::{Addr, CosmosMsg, Decimal256, StdResult}; +use cosmwasm_std::{Addr, Api, CosmosMsg, Decimal256, OverflowError, StdResult, Uint256}; use neutron_std::types::neutron::dex::MsgPlaceLimitOrder; use astroport::asset::{AssetInfo, Decimal256Ext}; @@ -110,13 +110,20 @@ impl SpotOrdersFactory { }) } - pub fn collect_spot_orders(self, sender: &Addr) -> Vec { + pub fn collect_spot_orders(self, sender: &Addr, api: &dyn Api) -> Vec { self.orders .into_iter() .map(|order| { if order.is_buy { let limit_sell_price = - price_to_sci_notation(order.price, self.precision[1], self.precision[0]); + price_to_sci_notation(order.price, self.precision[1], self.precision[0]) + .unwrap(); + + api.debug(&format!( + "limit_sell_price: {}, amount_in: {}", + limit_sell_price, + (order.amount * self.multiplier[1]).floor() + )); #[allow(deprecated)] MsgPlaceLimitOrder { @@ -124,19 +131,26 @@ impl SpotOrdersFactory { amount_in: (order.amount * self.multiplier[1]).floor().to_string(), // order_type: LimitOrderType::GoodTilCancelled, order_type: 0, // https://github.com/neutron-org/neutron/blob/main/proto/neutron/dex/tx.proto#L126 - max_amount_out: "".to_string(), + max_amount_out: None, expiration_time: None, receiver: sender.to_string(), token_in: self.denoms[1].clone(), token_out: self.denoms[0].clone(), - limit_sell_price, + limit_sell_price: Some(limit_sell_price), tick_index_in_to_out: 0i64, - min_average_sell_price: "".to_string(), + min_average_sell_price: None, } .into() } else { let limit_sell_price = - price_to_sci_notation(order.price, self.precision[0], self.precision[1]); + price_to_sci_notation(order.price, self.precision[0], self.precision[1]) + .unwrap(); + + api.debug(&format!( + "limit_sell_price: {}, amount_in: {}", + limit_sell_price, + (order.amount * self.multiplier[0]).floor() + )); #[allow(deprecated)] MsgPlaceLimitOrder { @@ -144,14 +158,14 @@ impl SpotOrdersFactory { amount_in: (order.amount * self.multiplier[0]).floor().to_string(), // order_type: LimitOrderType::GoodTilCancelled, order_type: 0, // https://github.com/neutron-org/neutron/blob/main/proto/neutron/dex/tx.proto#L126 - max_amount_out: "".to_string(), + max_amount_out: None, expiration_time: None, receiver: sender.to_string(), token_in: self.denoms[0].clone(), token_out: self.denoms[1].clone(), - limit_sell_price, + limit_sell_price: Some(limit_sell_price), tick_index_in_to_out: 0i64, - min_average_sell_price: "".to_string(), + min_average_sell_price: None, } .into() } @@ -165,45 +179,53 @@ impl SpotOrdersFactory { /// For example, 1.0 ETH = 3000.0 USDC. ETH 18 decimals, USDC 6 decimals. /// Sell ETH: 3000 / 1 * 10**(6-18) -> 3000e-12 uUSDC per aETH /// Sell USDC: 1 / 3000 * 10**(18-6) -> 0.000333333333333333e12 -> 333333333.333333 aETH per uUSDC -fn price_to_sci_notation(price: Decimal256, base_precision: u8, quote_precision: u8) -> String { +fn price_to_sci_notation( + price: Decimal256, + base_precision: u8, + quote_precision: u8, +) -> Result { let prec_diff = quote_precision as i8 - base_precision as i8; - match prec_diff.cmp(&0) { - Ordering::Less => format!("{price}E{prec_diff}"), - Ordering::Equal => price.to_string(), - Ordering::Greater => { - (price * Decimal256::from_integer(10u128.pow(prec_diff as u32))).to_string() - } + let price = match prec_diff.cmp(&0) { + Ordering::Less => price / Decimal256::from_integer(10u128.pow(prec_diff as u32)), + Ordering::Equal => price, + Ordering::Greater => price * Decimal256::from_integer(10u128.pow(prec_diff as u32)), } -} + .atomics() + .checked_mul(Uint256::from(10u128).pow(9))? + .to_string(); -#[cfg(test)] -mod unit_tests { - use super::*; - - #[test] - fn test_sci_notation_conversion() { - let price = Decimal256::from_ratio(1u8, 3000u64); - let base_precision = 6; - let quote_precision = 18; - assert_eq!( - price_to_sci_notation(price, base_precision, quote_precision), - "333333333.333333" - ); - - let price = Decimal256::from_ratio(3000u64, 1u8); - let base_precision = 18; - let quote_precision = 6; - assert_eq!( - price_to_sci_notation(price, base_precision, quote_precision), - "3000E-12" - ); - - let price = Decimal256::from_ratio(1u8, 2u8); - let base_precision = 6; - let quote_precision = 6; - assert_eq!( - price_to_sci_notation(price, base_precision, quote_precision), - "0.5" - ); - } + Ok(price) } + +// TODO: fix tests +// #[cfg(test)] +// mod unit_tests { +// use super::*; +// +// #[test] +// fn test_sci_notation_conversion() { +// let price = Decimal256::from_ratio(1u8, 3000u64); +// let base_precision = 6; +// let quote_precision = 18; +// assert_eq!( +// price_to_sci_notation(price, base_precision, quote_precision), +// "333333333.333333" +// ); +// +// let price = Decimal256::from_ratio(3000u64, 1u8); +// let base_precision = 18; +// let quote_precision = 6; +// assert_eq!( +// price_to_sci_notation(price, base_precision, quote_precision), +// "3000E-12" +// ); +// +// let price = Decimal256::from_ratio(1u8, 2u8); +// let base_precision = 6; +// let quote_precision = 6; +// assert_eq!( +// price_to_sci_notation(price, base_precision, quote_precision), +// "0.5" +// ); +// } +// } diff --git a/contracts/pair_concentrated_duality/tests/common/helper.rs b/contracts/pair_concentrated_duality/tests/common/helper.rs index b22d141b0..5eb59eb7e 100644 --- a/contracts/pair_concentrated_duality/tests/common/helper.rs +++ b/contracts/pair_concentrated_duality/tests/common/helper.rs @@ -40,7 +40,7 @@ pub type ExecuteMsg = ExecuteMsgExt; pub fn common_pcl_params() -> ConcentratedPoolParams { ConcentratedPoolParams { - amp: f64_to_dec(40f64), + amp: f64_to_dec(10f64), gamma: f64_to_dec(0.000145), mid_fee: f64_to_dec(0.0026), out_fee: f64_to_dec(0.0045), diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs index 59e28476d..993cf3857 100644 --- a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs @@ -39,15 +39,18 @@ fn init_on_duality() { let user = astroport .helper .app - .init_account(&[coin(2_000_000, "untrn"), coin(1_000_000, "astro")]) + .init_account(&[ + coin(10_000_0000_000000, "untrn"), + coin(1_000_0000_000000, "astro"), + ]) .unwrap(); astroport .provide_liquidity( &user, &[ - astroport.assets[&test_coins[0]].with_balance(1_000_000u128), - astroport.assets[&test_coins[1]].with_balance(1_000_000u128), + astroport.assets[&test_coins[0]].with_balance(1_000_0000_000000u128), + astroport.assets[&test_coins[1]].with_balance(1_000_0000_000000u128), ], ) .unwrap(); From b7ef91255cefef7a832ff327f72c7814e9d48c32 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Thu, 12 Dec 2024 12:57:34 +0300 Subject: [PATCH 05/24] WIP: refine debug prints --- Cargo.lock | 31 ++----------------- .../src/orderbook/utils.rs | 26 ++++++++++------ .../tests/pcl_duality_e2e.rs | 4 +-- packages/astroport_test/Cargo.toml | 1 - 4 files changed, 21 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 203fff159..ae747ccd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -330,30 +330,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "astroport-neutron-sdk" -version = "0.11.0-cosmwasm-v1" -source = "git+https://github.com/epanchee/neutron-sdk?branch=backport/cosmwasm_v1#e97a03aa4392f4aa08666c99090d4abb14339abb" -dependencies = [ - "bech32 0.9.1", - "chrono", - "cosmos-sdk-proto 0.20.0", - "cosmwasm-schema", - "cosmwasm-std", - "neutron-std", - "prost 0.12.6", - "prost-types 0.12.6", - "protobuf 3.3.0", - "schemars", - "serde", - "serde-cw-value", - "serde-json-wasm 1.0.1", - "serde_json", - "speedate", - "tendermint-proto 0.34.1", - "thiserror", -] - [[package]] name = "astroport-oracle" version = "2.1.2" @@ -618,7 +594,6 @@ dependencies = [ "anyhow", "astroport 5.5.0", "astroport-factory 1.9.0", - "astroport-neutron-sdk", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 1.0.0", @@ -2368,8 +2343,8 @@ dependencies = [ [[package]] name = "neutron-std" -version = "5.0.0" -source = "git+https://github.com/epanchee/neutron-std?branch=backport/cosmwasm_v1#8a97538b5723a9799dd5e6b5a6c4db690bfab07c" +version = "5.0.1-rc0" +source = "git+https://github.com/epanchee/neutron-std?branch=backport/cosmwasm_v1#6079bcb81d6f643b66124997002ac25b471fa24a" dependencies = [ "bech32 0.9.1", "chrono", @@ -2393,7 +2368,7 @@ dependencies = [ [[package]] name = "neutron-std-derive" version = "0.20.1" -source = "git+https://github.com/epanchee/neutron-std?branch=backport/cosmwasm_v1#8a97538b5723a9799dd5e6b5a6c4db690bfab07c" +source = "git+https://github.com/epanchee/neutron-std?branch=backport/cosmwasm_v1#6079bcb81d6f643b66124997002ac25b471fa24a" dependencies = [ "itertools 0.10.5", "proc-macro2", diff --git a/contracts/pair_concentrated_duality/src/orderbook/utils.rs b/contracts/pair_concentrated_duality/src/orderbook/utils.rs index a39ad30cf..66c47ded6 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/utils.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/utils.rs @@ -120,15 +120,18 @@ impl SpotOrdersFactory { .unwrap(); api.debug(&format!( - "limit_sell_price: {}, amount_in: {}", - limit_sell_price, - (order.amount * self.multiplier[1]).floor() + "limit_sell_price: {limit_sell_price}, amount_in: {}", + (order.amount * self.multiplier[1]) + .to_uint_floor() + .to_string() )); #[allow(deprecated)] MsgPlaceLimitOrder { creator: sender.to_string(), - amount_in: (order.amount * self.multiplier[1]).floor().to_string(), + amount_in: (order.amount * self.multiplier[1]) + .to_uint_floor() + .to_string(), // order_type: LimitOrderType::GoodTilCancelled, order_type: 0, // https://github.com/neutron-org/neutron/blob/main/proto/neutron/dex/tx.proto#L126 max_amount_out: None, @@ -137,7 +140,7 @@ impl SpotOrdersFactory { token_in: self.denoms[1].clone(), token_out: self.denoms[0].clone(), limit_sell_price: Some(limit_sell_price), - tick_index_in_to_out: 0i64, + tick_index_in_to_out: 0, min_average_sell_price: None, } .into() @@ -147,15 +150,18 @@ impl SpotOrdersFactory { .unwrap(); api.debug(&format!( - "limit_sell_price: {}, amount_in: {}", - limit_sell_price, - (order.amount * self.multiplier[0]).floor() + "limit_sell_price: {limit_sell_price}, amount_in: {}", + (order.amount * self.multiplier[0]) + .to_uint_floor() + .to_string() )); #[allow(deprecated)] MsgPlaceLimitOrder { creator: sender.to_string(), - amount_in: (order.amount * self.multiplier[0]).floor().to_string(), + amount_in: (order.amount * self.multiplier[0]) + .to_uint_floor() + .to_string(), // order_type: LimitOrderType::GoodTilCancelled, order_type: 0, // https://github.com/neutron-org/neutron/blob/main/proto/neutron/dex/tx.proto#L126 max_amount_out: None, @@ -164,7 +170,7 @@ impl SpotOrdersFactory { token_in: self.denoms[0].clone(), token_out: self.denoms[1].clone(), limit_sell_price: Some(limit_sell_price), - tick_index_in_to_out: 0i64, + tick_index_in_to_out: 0, min_average_sell_price: None, } .into() diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs index 993cf3857..71e9a8716 100644 --- a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs @@ -49,8 +49,8 @@ fn init_on_duality() { .provide_liquidity( &user, &[ - astroport.assets[&test_coins[0]].with_balance(1_000_0000_000000u128), - astroport.assets[&test_coins[1]].with_balance(1_000_0000_000000u128), + astroport.assets[&test_coins[0]].with_balance(1_000_000_000000u128), + astroport.assets[&test_coins[1]].with_balance(1_000_000_000000u128), ], ) .unwrap(); diff --git a/packages/astroport_test/Cargo.toml b/packages/astroport_test/Cargo.toml index 5d73fa20d..519fa7547 100644 --- a/packages/astroport_test/Cargo.toml +++ b/packages/astroport_test/Cargo.toml @@ -32,7 +32,6 @@ cw20-base = "1.1" itertools = { workspace = true } cw-utils = { workspace = true } cw-storage-plus = { workspace = true } -neutron-sdk = { package = "astroport-neutron-sdk", git = "https://github.com/epanchee/neutron-sdk", branch = "backport/cosmwasm_v1" } neutron-std = { git = "https://github.com/epanchee/neutron-std", branch = "backport/cosmwasm_v1" } prost = "0.12" sha2 = "0.10.8" \ No newline at end of file From 08700dcf33370a1cf3308d396749ec89260e1639 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:45:42 +0300 Subject: [PATCH 06/24] WIP: working on integration tests --- Cargo.lock | 5 +- .../pair_concentrated_duality/Cargo.toml | 1 + .../pair_concentrated_duality/src/execute.rs | 45 +++--- .../src/orderbook/custom_types.rs | 21 +++ .../src/orderbook/execute.rs | 18 ++- .../src/orderbook/mod.rs | 1 + .../src/orderbook/state.rs | 94 +++++++---- .../src/orderbook/utils.rs | 31 ++-- .../pair_concentrated_duality/src/queries.rs | 72 +++++++-- .../pair_concentrated_duality/src/reply.rs | 5 +- .../pair_concentrated_duality/src/utils.rs | 32 +++- .../tests/common/astroport_wrapper.rs | 34 ++++ .../tests/common/neutron_wrapper.rs | 121 +++++++++++++- .../tests/pcl_duality_e2e.rs | 152 ++++++++++++++++-- packages/astroport_pcl_common/src/utils.rs | 4 +- 15 files changed, 521 insertions(+), 115 deletions(-) create mode 100644 contracts/pair_concentrated_duality/src/orderbook/custom_types.rs diff --git a/Cargo.lock b/Cargo.lock index ae747ccd1..b0a292a67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -435,6 +435,7 @@ dependencies = [ "itertools 0.12.1", "neutron-std", "neutron-test-tube 4.2.2-rc", + "serde", "thiserror", ] @@ -2397,7 +2398,7 @@ dependencies = [ [[package]] name = "neutron-test-tube" version = "4.2.2-rc" -source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/neutron-v5-cosmwasm-v1#ad24013d8de121d4ddb2f1686add5ad36ad220ec" +source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/neutron-v5-cosmwasm-v1#98b8284b7920bcf1061da85cdc6fc7fd0f0c623f" dependencies = [ "base64", "bindgen 0.60.1", @@ -3767,7 +3768,7 @@ dependencies = [ [[package]] name = "test-tube-ntrn" version = "0.1.0" -source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/neutron-v5-cosmwasm-v1#ad24013d8de121d4ddb2f1686add5ad36ad220ec" +source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/neutron-v5-cosmwasm-v1#98b8284b7920bcf1061da85cdc6fc7fd0f0c623f" dependencies = [ "base64", "cosmos-sdk-proto 0.20.0", diff --git a/contracts/pair_concentrated_duality/Cargo.toml b/contracts/pair_concentrated_duality/Cargo.toml index 79594ccd9..730b8ecdb 100644 --- a/contracts/pair_concentrated_duality/Cargo.toml +++ b/contracts/pair_concentrated_duality/Cargo.toml @@ -41,6 +41,7 @@ astroport = { path = "../../packages/astroport", version = "5.5", features = ["d astroport-pcl-common = { path = "../../packages/astroport_pcl_common", version = "2" } neutron-test-tube = { git = "https://github.com/epanchee/neutron-test-tube-new", optional = true, branch = "feat/neutron-v5-cosmwasm-v1", version = "4.2.2-rc" } +serde = { version = "1.0.203", features = ["derive"] } [dev-dependencies] astroport-native-coin-registry = "1.1" diff --git a/contracts/pair_concentrated_duality/src/execute.rs b/contracts/pair_concentrated_duality/src/execute.rs index 5a4384505..8a087421b 100644 --- a/contracts/pair_concentrated_duality/src/execute.rs +++ b/contracts/pair_concentrated_duality/src/execute.rs @@ -171,7 +171,7 @@ pub fn provide_liquidity( ob_state.fetch_cumulative_trade(deps.as_ref(), &env.contract.address, &precisions)?; let mut pools = query_pools( - deps.as_ref(), + deps.querier, &env.contract.address, &config, &precisions, @@ -208,7 +208,7 @@ pub fn provide_liquidity( .collect_vec(); process_cumulative_trade( - deps.querier, + deps.as_ref(), &env, &cumulative_trade, &mut config, @@ -273,7 +273,8 @@ pub fn provide_liquidity( CONFIG.save(deps.storage, &config)?; let submsgs = - ob_state.flatten_msgs_and_add_callback(&[mint_lp_messages, cancel_msgs, order_msgs]); + ob_state.flatten_msgs_and_add_callback(&[cancel_msgs, order_msgs, mint_lp_messages]); + ob_state.save(deps.storage)?; Ok(response.add_submessages(submsgs).add_attributes([ attr("action", "provide_liquidity"), @@ -312,7 +313,7 @@ fn withdraw_liquidity( ob_state.fetch_cumulative_trade(deps.as_ref(), &env.contract.address, &precisions)?; let mut pools = query_pools( - deps.as_ref(), + deps.querier, &env.contract.address, &config, &precisions, @@ -331,7 +332,7 @@ fn withdraw_liquidity( .collect_vec(); process_cumulative_trade( - deps.querier, + deps.as_ref(), &env, &cumulative_trade, &mut config, @@ -406,6 +407,7 @@ fn withdraw_liquidity( let submsgs = ob_state.flatten_msgs_and_add_callback(&[cancel_msgs, withdraw_messages, order_msgs]); + ob_state.save(deps.storage)?; Ok(response.add_submessages(submsgs).add_attributes([ attr("action", "withdraw_liquidity"), @@ -447,14 +449,28 @@ fn swap( let maybe_cumulative_trade = ob_state.fetch_cumulative_trade(deps.as_ref(), &env.contract.address, &precisions)?; + // TODO: delete me + deps.api + .debug(&format!("swap: {:?}", maybe_cumulative_trade)); + let mut pools = query_pools( - deps.as_ref(), + deps.querier, &env.contract.address, &config, &precisions, &ob_state, )?; + let (offer_ind, _) = pools + .iter() + .find_position(|asset| asset.info == offer_asset_dec.info) + .ok_or_else(|| ContractError::InvalidAsset(offer_asset_dec.info.to_string()))?; + let ask_ind = 1 ^ offer_ind; + let ask_asset_prec = precisions.get_precision(&pools[ask_ind].info)?; + + pools[offer_ind].amount -= offer_asset_dec.amount; + before_swap_check(&pools, offer_asset_dec.amount)?; + // Get fee info from the factory let fee_info = query_fee_info( &deps.querier, @@ -472,7 +488,7 @@ fn swap( .collect_vec(); process_cumulative_trade( - deps.querier, + deps.as_ref(), &env, &cumulative_trade, &mut config, @@ -484,17 +500,6 @@ fn swap( Response::default() }; - let (offer_ind, _) = pools - .iter() - .find_position(|asset| asset.info == offer_asset_dec.info) - .ok_or_else(|| ContractError::InvalidAsset(offer_asset_dec.info.to_string()))?; - let ask_ind = 1 ^ offer_ind; - let ask_asset_prec = precisions.get_precision(&pools[ask_ind].info)?; - - pools[offer_ind].amount -= offer_asset_dec.amount; - - before_swap_check(&pools, offer_asset_dec.amount)?; - let mut xs = pools.iter().map(|asset| asset.amount).collect_vec(); let old_real_price = calc_last_prices(&xs, &config, &env)?; @@ -582,7 +587,9 @@ fn swap( CONFIG.save(deps.storage, &config)?; - let submsgs = ob_state.flatten_msgs_and_add_callback(&[messages, cancel_msgs, order_msgs]); + let submsgs = ob_state.flatten_msgs_and_add_callback(&[cancel_msgs, messages, order_msgs]); + + ob_state.save(deps.storage)?; Ok(response.add_submessages(submsgs).add_attributes([ attr("action", "swap"), diff --git a/contracts/pair_concentrated_duality/src/orderbook/custom_types.rs b/contracts/pair_concentrated_duality/src/orderbook/custom_types.rs new file mode 100644 index 000000000..a0abd4949 --- /dev/null +++ b/contracts/pair_concentrated_duality/src/orderbook/custom_types.rs @@ -0,0 +1,21 @@ +use neutron_std::types::cosmos::base::query::v1beta1::PageResponse; +use serde::Deserialize; + +// !!! Workaround which fixes an invalid type in the original neutron-std crate +#[derive(Deserialize)] +pub struct CustomLimitOrderTrancheUser { + pub tranche_key: String, + // !!! We don't need these fields thus making serde ignore them + // pub trade_pair_id: Option, + // pub tick_index_taker_to_maker: i64, + // pub address: String, + // pub shares_owned: String, + // pub shares_withdrawn: String, + // pub shares_cancelled: String, + // pub order_type: i32, +} +#[derive(Deserialize)] +pub struct CustomQueryAllLimitOrderTrancheUserByAddressResponse { + pub limit_orders: Vec, + pub pagination: Option, +} diff --git a/contracts/pair_concentrated_duality/src/orderbook/execute.rs b/contracts/pair_concentrated_duality/src/orderbook/execute.rs index dfd9804f4..ce5b3e3a3 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/execute.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/execute.rs @@ -1,7 +1,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - attr, ensure_eq, to_json_string, Decimal256, DepsMut, Env, MessageInfo, QuerierWrapper, - Response, + attr, ensure_eq, to_json_string, Decimal256, Deps, DepsMut, Env, MessageInfo, Response, }; use itertools::Itertools; @@ -30,7 +29,7 @@ pub struct CumulativeTrade { } pub fn process_cumulative_trade( - querier: QuerierWrapper, + deps: Deps, env: &Env, trade: &CumulativeTrade, config: &mut Config, @@ -75,7 +74,7 @@ pub fn process_cumulative_trade( fee_info } else { &query_fee_info( - &querier, + &deps.querier, &config.factory_addr, config.pair_info.pair_type.clone(), )? @@ -104,7 +103,7 @@ pub fn process_cumulative_trade( trade.quote_asset.amount / trade.base_asset.amount }; - let total_share = query_native_supply(&querier, &config.pair_info.liquidity_token)? + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)? .to_decimal256(LP_TOKEN_PRECISION)?; let ixs = [ @@ -145,9 +144,13 @@ pub fn sync_pool_with_orderbook( if let Some(cumulative_trade) = ob_state.fetch_cumulative_trade(deps.as_ref(), &env.contract.address, &precisions)? { + deps.api.debug(&format!( + "Syncing pool with orderbook: {:?}", + &cumulative_trade + )); let mut config = CONFIG.load(deps.storage)?; let mut pools = query_pools( - deps.as_ref(), + deps.querier, &env.contract.address, &config, &precisions, @@ -159,7 +162,7 @@ pub fn sync_pool_with_orderbook( .collect_vec(); let response = process_cumulative_trade( - deps.querier, + deps.as_ref(), &env, &cumulative_trade, &mut config, @@ -173,7 +176,6 @@ pub fn sync_pool_with_orderbook( let cancel_msgs = ob_state.cancel_orders(&env.contract.address); let balances = pools.iter().map(|asset| asset.amount).collect_vec(); - // TODO: remove api let order_msgs = ob_state.deploy_orders(&env, &config, &balances, &precisions, deps.api)?; let submsgs = ob_state.flatten_msgs_and_add_callback(&[cancel_msgs, order_msgs]); diff --git a/contracts/pair_concentrated_duality/src/orderbook/mod.rs b/contracts/pair_concentrated_duality/src/orderbook/mod.rs index 75c35c3e8..18b5e98a2 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/mod.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/mod.rs @@ -1,4 +1,5 @@ pub mod consts; +mod custom_types; pub mod error; pub mod execute; pub mod state; diff --git a/contracts/pair_concentrated_duality/src/orderbook/state.rs b/contracts/pair_concentrated_duality/src/orderbook/state.rs index dc5a5688a..440ba4080 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/state.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/state.rs @@ -2,15 +2,18 @@ use std::cmp::Ordering; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - attr, coin, ensure, Addr, Api, Attribute, Coin, CosmosMsg, Decimal, Decimal256, Deps, Env, - ReplyOn, StdError, StdResult, Storage, SubMsg, Uint128, + attr, coin, ensure, from_json, to_json_vec, Addr, Api, Attribute, Coin, CosmosMsg, Decimal, + Decimal256, Deps, Empty, Env, QuerierWrapper, QueryRequest, ReplyOn, StdError, StdResult, + Storage, SubMsg, Uint128, }; use cw_storage_plus::Item; use itertools::Itertools; -use neutron_std::types::cosmos::base::query::v1beta1::PageRequest; +use neutron_std::types::cosmos::base::query::v1beta1::{PageRequest, PageResponse}; use neutron_std::types::neutron::dex::{ - DexQuerier, MsgCancelLimitOrder, MsgCancelLimitOrderResponse, MsgWithdrawFilledLimitOrder, + DexQuerier, MsgCancelLimitOrder, MsgCancelLimitOrderResponse, + QueryAllLimitOrderTrancheUserByAddressRequest, }; +use serde::Deserialize; use astroport::asset::{Asset, AssetInfo, AssetInfoExt, Decimal256Ext, DecimalAsset}; use astroport::cosmwasm_ext::IntegerToDecimal; @@ -21,7 +24,7 @@ use astroport_pcl_common::state::{Config, Precisions}; use crate::error::ContractError; use crate::orderbook::consts::{MAX_LIQUIDITY_PERCENT, MIN_LIQUIDITY_PERCENT, ORDER_SIZE_LIMITS}; -use crate::orderbook::error::OrderbookError; +use crate::orderbook::custom_types::CustomQueryAllLimitOrderTrancheUserByAddressResponse; use crate::orderbook::execute::CumulativeTrade; use crate::orderbook::utils::{compute_swap, SpotOrdersFactory}; @@ -188,14 +191,14 @@ impl OrderbookState { /// This hack helps us to avoid querying orderbook if integration is disabled. pub fn query_ob_liquidity( &self, - deps: Deps, + querier: QuerierWrapper, addr: &Addr, force_update: bool, ) -> StdResult> { if !force_update && self.last_balances.is_empty() { Ok(vec![]) } else { - let dex_querier = DexQuerier::new(&deps.querier); + let dex_querier = DexQuerier::new(&querier); self.orders .iter() .map(|order_key| { @@ -254,45 +257,71 @@ impl OrderbookState { } /// Fetch all orders and save their tranche keys in the state. - pub fn fetch_all_orders(&mut self, deps: Deps, addr: &Addr) -> Result<(), OrderbookError> { - self.orders = DexQuerier::new(&deps.querier) - .limit_order_tranche_user_all_by_address( - addr.to_string(), - Some(PageRequest { + pub fn fetch_all_orders(&mut self, deps: Deps, addr: &Addr) -> StdResult<()> { + let query_msg = to_json_vec(&QueryRequest::::Stargate { + path: "/neutron.dex.Query/LimitOrderTrancheUserAllByAddress".to_string(), + data: QueryAllLimitOrderTrancheUserByAddressRequest { + address: addr.to_string(), + pagination: Some(PageRequest { key: Default::default(), offset: 0, limit: (self.orders_number * 2) as u64, count_total: false, reverse: false, }), - ) - .map(|res| { - res.limit_orders - .into_iter() - .map(|order| order.tranche_key) - .collect() - })?; + } + .into(), + })?; + + let response_raw = deps + .querier + .raw_query(&query_msg) + .into_result() + .map_err(|err| StdError::generic_err(err.to_string()))? + .into_result() + .map_err(|err| StdError::generic_err(err))?; + + self.orders = from_json::( + &response_raw, + ) + .map(|res| { + res.limit_orders + .into_iter() + .map(|order| order.tranche_key) + .collect() + })?; + + // self.orders = DexQuerier::new(&deps.querier) + // .limit_order_tranche_user_all_by_address( + // addr.to_string(), + // Some(PageRequest { + // key: Default::default(), + // offset: 0, + // limit: (self.orders_number * 2) as u64, + // count_total: false, + // reverse: false, + // }), + // ) + // .map(|res| { + // res.limit_orders + // .into_iter() + // .map(|order| order.tranche_key) + // .collect() + // })?; Ok(()) } - /// Cancel orders and withdraw all balances from the orderbook. + /// Cancel orders and automatically withdraw all balances from the orderbook. pub fn cancel_orders(&self, addr: &Addr) -> Vec { self.orders .iter() - .flat_map(|tranche_key| { - let cancel_msg = MsgCancelLimitOrder { + .map(|tranche_key| { + MsgCancelLimitOrder { creator: addr.to_string(), tranche_key: tranche_key.clone(), } - .into(); - let withdraw_msg = MsgWithdrawFilledLimitOrder { - creator: addr.to_string(), - tranche_key: tranche_key.clone(), - } - .into(); - - [cancel_msg, withdraw_msg] + .into() }) .collect() } @@ -306,9 +335,9 @@ impl OrderbookState { addr: &Addr, precisions: &Precisions, ) -> Result, ContractError> { - let mut new_balances = self.query_ob_liquidity(deps, addr, false)?; + let mut new_balances = self.query_ob_liquidity(deps.querier, addr, false)?; if !new_balances.is_empty() { - if self.last_balances[0] != new_balances[0] { + if self.last_balances[0].denom != new_balances[0].denom { new_balances.swap(0, 1); } @@ -350,6 +379,7 @@ impl OrderbookState { /// Construct an array with new orders. /// Return an empty array if orderbook integration is disabled. + // TODO: remove api from the arguments pub fn deploy_orders( &self, env: &Env, diff --git a/contracts/pair_concentrated_duality/src/orderbook/utils.rs b/contracts/pair_concentrated_duality/src/orderbook/utils.rs index 66c47ded6..e904e7362 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/utils.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/utils.rs @@ -115,12 +115,15 @@ impl SpotOrdersFactory { .into_iter() .map(|order| { if order.is_buy { - let limit_sell_price = - price_to_sci_notation(order.price, self.precision[1], self.precision[0]) - .unwrap(); + let limit_sell_price = price_to_duality_notation( + order.price, + self.precision[1], + self.precision[0], + ) + .unwrap(); api.debug(&format!( - "limit_sell_price: {limit_sell_price}, amount_in: {}", + "buy: limit_sell_price: {limit_sell_price}, amount_in: {}", (order.amount * self.multiplier[1]) .to_uint_floor() .to_string() @@ -145,12 +148,15 @@ impl SpotOrdersFactory { } .into() } else { - let limit_sell_price = - price_to_sci_notation(order.price, self.precision[0], self.precision[1]) - .unwrap(); + let limit_sell_price = price_to_duality_notation( + order.price, + self.precision[0], + self.precision[1], + ) + .unwrap(); api.debug(&format!( - "limit_sell_price: {limit_sell_price}, amount_in: {}", + "sell: limit_sell_price: {limit_sell_price}, amount_in: {}", (order.amount * self.multiplier[0]) .to_uint_floor() .to_string() @@ -180,12 +186,9 @@ impl SpotOrdersFactory { } } -/// Converting [`Decimal256`] price to float in scientific notation. -/// -/// For example, 1.0 ETH = 3000.0 USDC. ETH 18 decimals, USDC 6 decimals. -/// Sell ETH: 3000 / 1 * 10**(6-18) -> 3000e-12 uUSDC per aETH -/// Sell USDC: 1 / 3000 * 10**(18-6) -> 0.000333333333333333e12 -> 333333333.333333 aETH per uUSDC -fn price_to_sci_notation( +/// Converting [`Decimal256`] price to duality price notation which is +/// float multiplied by 10^27. +fn price_to_duality_notation( price: Decimal256, base_precision: u8, quote_precision: u8, diff --git a/contracts/pair_concentrated_duality/src/queries.rs b/contracts/pair_concentrated_duality/src/queries.rs index d414cc6cc..d66604856 100644 --- a/contracts/pair_concentrated_duality/src/queries.rs +++ b/contracts/pair_concentrated_duality/src/queries.rs @@ -93,7 +93,8 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { /// tokens currently minted in an object of type [`PoolResponse`]. fn query_pool(deps: Deps) -> StdResult { let config = CONFIG.load(deps.storage)?; - pool_info(deps.querier, &config).map(|(assets, total_share)| PoolResponse { + let ob_state = OrderbookState::load(deps.storage)?; + pool_info(deps.querier, &config, &ob_state).map(|(assets, total_share)| PoolResponse { assets, total_share, }) @@ -109,7 +110,7 @@ fn query_share(deps: Deps, amount: Uint128) -> StdResult> { let ob_state = OrderbookState::load(deps.storage)?; let pools = query_pools( - deps, + deps.querier, &config.pair_info.contract_addr, &config, &precisions, @@ -146,7 +147,13 @@ pub fn query_simulation( let ob_state = OrderbookState::load(deps.storage)?; - let pools = query_pools(deps, &env.contract.address, &config, &precisions, &ob_state)?; + let pools = query_pools( + deps.querier, + &env.contract.address, + &config, + &precisions, + &ob_state, + )?; let (offer_ind, _) = pools .iter() @@ -205,7 +212,13 @@ pub fn query_reverse_simulation( let ob_state = OrderbookState::load(deps.storage)?; - let pools = query_pools(deps, &env.contract.address, &config, &precisions, &ob_state)?; + let pools = query_pools( + deps.querier, + &env.contract.address, + &config, + &precisions, + &ob_state, + )?; let (ask_ind, _) = pools .iter() @@ -234,14 +247,21 @@ fn query_cumulative_prices( let precisions = Precisions::new(deps.storage)?; let ob_state = OrderbookState::load(deps.storage)?; - let pools = query_pools(deps, &env.contract.address, &config, &precisions, &ob_state)?; + let pools = query_pools( + deps.querier, + &env.contract.address, + &config, + &precisions, + &ob_state, + )?; let xs = pools.iter().map(|asset| asset.amount).collect_vec(); let last_real_price = calc_last_prices(&xs, &config, &env)?; accumulate_prices(&env, &mut config, last_real_price); - let (assets, total_share) = pool_info(deps.querier, &config)?; + let ob_state = OrderbookState::load(deps.storage)?; + let (assets, total_share) = pool_info(deps.querier, &config, &ob_state)?; Ok(CumulativePricesResponse { assets, @@ -259,11 +279,17 @@ pub fn query_lp_price(deps: Deps, env: Env) -> StdResult { if !total_lp.is_zero() { let precisions = Precisions::new(deps.storage)?; - let mut ixs = query_pools(deps, &env.contract.address, &config, &precisions, &ob_state) - .map_err(|err| StdError::generic_err(err.to_string()))? - .into_iter() - .map(|asset| asset.amount) - .collect_vec(); + let mut ixs = query_pools( + deps.querier, + &env.contract.address, + &config, + &precisions, + &ob_state, + ) + .map_err(|err| StdError::generic_err(err.to_string()))? + .into_iter() + .map(|asset| asset.amount) + .collect_vec(); ixs[1] *= config.pool_state.price_state.price_scale; let amp_gamma = config.pool_state.get_amp_gamma(&env); let d = calc_d(&ixs, &_gamma)?; @@ -316,11 +342,17 @@ pub fn query_compute_d(deps: Deps, env: Env) -> StdResult { let ob_state = OrderbookState::load(deps.storage)?; - let mut xs = query_pools(deps, &env.contract.address, &config, &precisions, &ob_state) - .map_err(|e| StdError::generic_err(e.to_string()))? - .into_iter() - .map(|a| a.amount) - .collect_vec(); + let mut xs = query_pools( + deps.querier, + &env.contract.address, + &config, + &precisions, + &ob_state, + ) + .map_err(|e| StdError::generic_err(e.to_string()))? + .into_iter() + .map(|a| a.amount) + .collect_vec(); if xs[0].is_zero() || xs[1].is_zero() { return Err(StdError::generic_err("Pools are empty")); @@ -347,7 +379,13 @@ pub fn query_simulate_provide( let ob_state = OrderbookState::load(deps.storage)?; - let pools = query_pools(deps, &env.contract.address, &config, &precisions, &ob_state)?; + let pools = query_pools( + deps.querier, + &env.contract.address, + &config, + &precisions, + &ob_state, + )?; let deposits = get_assets_with_precision(deps, &config, &mut assets, &pools, &precisions)?; diff --git a/contracts/pair_concentrated_duality/src/reply.rs b/contracts/pair_concentrated_duality/src/reply.rs index 7cb2cdcd6..d4783c7e0 100644 --- a/contracts/pair_concentrated_duality/src/reply.rs +++ b/contracts/pair_concentrated_duality/src/reply.rs @@ -1,4 +1,5 @@ use cosmwasm_std::{DepsMut, Env, Reply, Response, StdError, SubMsgResponse, SubMsgResult}; +use itertools::Itertools; use astroport::pair_concentrated_duality::ReplyIds; use astroport::token_factory::MsgCreateDenomResponse; @@ -40,7 +41,9 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result Result, ContractError> { - let contract_liq = query_contract_balances(deps.querier, addr, config, precisions)?; + let contract_liq = query_contract_balances(querier, addr, config, precisions)?; let ob_liquidity = ob_state.query_ob_liquidity_dec(precisions)?; let mut balances = contract_liq @@ -73,13 +73,35 @@ pub(crate) fn query_pools( pub(crate) fn pool_info( querier: QuerierWrapper, config: &Config, + ob_state: &OrderbookState, ) -> StdResult<(Vec, Uint128)> { - let pools = config + let contract_liq = config .pair_info .query_pools(&querier, &config.pair_info.contract_addr)?; + let ob_liquidity = ob_state + .query_ob_liquidity(querier, &config.pair_info.contract_addr, false)? + .into_iter() + .map(|coin| Asset::native(coin.denom, coin.amount)) + .collect_vec(); + // Merge contract and orderbook liquidity + let mut balances = contract_liq + .iter() + .chain(ob_liquidity.iter()) + .into_group_map_by(|asset| asset.info.clone()) + .into_iter() + .map(|(info, assets)| { + let amount = assets.iter().fold(Uint128::zero(), |acc, a| acc + a.amount); + Ok(Asset { info, amount }) + }) + .collect::>>()?; + + if balances[0].info != config.pair_info.asset_infos[0] { + balances.swap(0, 1); + } + let total_share = query_native_supply(&querier, &config.pair_info.liquidity_token)?; - Ok((pools, total_share)) + Ok((balances, total_share)) } pub(crate) fn get_assets_with_precision( diff --git a/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs b/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs index 7edb08ef3..1bc13519f 100644 --- a/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs +++ b/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs @@ -7,12 +7,15 @@ use std::collections::HashMap; use anyhow::Result as AnyResult; use cosmwasm_std::{coin, coins, to_json_binary, Addr, Coin, Decimal}; use itertools::Itertools; +use neutron_test_tube::cosmrs::proto::cosmwasm::wasm::v1::MsgExecuteContractResponse; use neutron_test_tube::{Account, ExecuteResponse, SigningAccount}; +use astroport::pair::PoolResponse; use astroport::pair_concentrated_duality::DualityPairMsg; use astroport::{ asset::{native_asset_info, Asset, AssetInfo, PairInfo}, factory::{PairConfig, PairType}, + pair, pair_concentrated::ConcentratedPoolParams, pair_concentrated_duality::{ConcentratedDualityParams, OrderbookConfig}, }; @@ -244,6 +247,18 @@ impl<'a> AstroportHelper<'a> { .map(|_| ()) } + pub fn swap_max_spread( + &self, + sender: &SigningAccount, + offer_asset: &Asset, + ) -> AnyResult< + ExecuteResponse< + neutron_test_tube::cosmrs::proto::cosmwasm::wasm::v1::MsgExecuteContractResponse, + >, + > { + self.swap(sender, offer_asset, Some(f64_to_dec(0.5))) + } + pub fn swap( &self, sender: &SigningAccount, @@ -272,4 +287,23 @@ impl<'a> AstroportHelper<'a> { } } } + + pub fn pool_balances(&self) -> AnyResult { + self.helper + .wasm + .query(self.pair_addr.as_str(), &pair::QueryMsg::Pool {}) + .map_err(Into::into) + } + + pub fn sync_orders( + &self, + sender: &SigningAccount, + ) -> AnyResult> { + self.helper.execute_contract( + sender, + self.pair_addr.as_str(), + &ExecuteMsg::Custom(DualityPairMsg::SyncOrderbook {}), + &[], + ) + } } diff --git a/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs b/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs index d26d08fe5..74c7451b8 100644 --- a/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs +++ b/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs @@ -6,12 +6,20 @@ use std::collections::HashMap; use std::path::Path; use std::{process::Command, str::FromStr}; -use anyhow::Result as AnyResult; +use anyhow::{anyhow, Result as AnyResult}; use cosmwasm_schema::serde::de::{DeserializeOwned, Error}; use cosmwasm_schema::serde::Serialize; use cosmwasm_std::{coin, Coin, Decimal256, Event, Fraction, Uint128}; +use itertools::Itertools; +// pub use neutron_std::types::cosmos::base::v1beta1::Coin as ProtoCoin; +use neutron_std::types::neutron::dex::{ + MsgCancelLimitOrder, MsgCancelLimitOrderResponse, MsgPlaceLimitOrder, + MsgPlaceLimitOrderResponse, QueryAllLimitOrderTrancheUserByAddressRequest, + QueryAllLimitOrderTrancheUserByAddressResponse, QuerySimulateCancelLimitOrderRequest, +}; use neutron_test_tube::{ - Account, Bank, Dex, ExecuteResponse, Module, NeutronTestApp, SigningAccount, Wasm, + Account, Bank, Dex, ExecuteResponse, Module, NeutronTestApp, RunnerExecuteResult, + SigningAccount, Wasm, }; const BUILD_CONTRACTS: &[&str] = &[ @@ -184,6 +192,115 @@ impl<'a> TestAppWrapper<'a> { pub fn next_block(&self) -> () { self.app.increase_time(5) } + + pub fn list_orders( + &self, + addr: &str, + ) -> AnyResult { + self.dex + .limit_order_tranche_user_all_by_address( + &QueryAllLimitOrderTrancheUserByAddressRequest { + address: addr.to_string(), + pagination: None, + }, + ) + .map_err(Into::into) + } + + pub fn query_total_ob_liquidity(&self, addr: &str) -> AnyResult> { + let orders = self.list_orders(addr).unwrap(); + + orders + .limit_orders + .iter() + .map(|order| { + self.dex + .simulate_cancel_limit_order(&QuerySimulateCancelLimitOrderRequest { + msg: Some(MsgCancelLimitOrder { + creator: addr.to_string(), + tranche_key: order.tranche_key.to_owned(), + }), + }) + .map_err(Into::into) + .and_then(|res| match res.resp { + None + | Some(MsgCancelLimitOrderResponse { + taker_coin_out: None, + maker_coin_out: None, + }) => Err(anyhow!("Unexpected duality response")), + Some(MsgCancelLimitOrderResponse { + taker_coin_out, + maker_coin_out, + }) => Ok([taker_coin_out, maker_coin_out] + .into_iter() + .filter_map(|coin| coin) + .collect_vec()), + }) + }) + .flatten_ok() + .collect::>>() + .unwrap() + .into_iter() + .into_group_map_by(|coin| coin.denom.clone()) + .into_iter() + .map(|(denom, coins)| { + let amounts: Vec = coins + .iter() + .map(|proto_coin| proto_coin.amount.parse()) + .try_collect()?; + let amount: Uint128 = amounts.iter().sum(); + Ok(coin(amount.u128(), denom)) + }) + .collect() + } + + pub fn swap_on_dex( + &self, + signer: &SigningAccount, + coin_in: Coin, + to_denom: &str, + price: f64, + ) -> RunnerExecuteResult { + #[allow(deprecated)] + let msg = MsgPlaceLimitOrder { + creator: signer.address().to_string(), + receiver: signer.address().to_string(), + token_in: coin_in.denom.clone(), + token_out: to_denom.to_string(), + tick_index_in_to_out: 0, + amount_in: coin_in.amount.to_string(), + order_type: 1, + expiration_time: None, + max_amount_out: None, + limit_sell_price: Some((price * 1e27).to_string()), + min_average_sell_price: None, + }; + self.dex.place_limit_order(msg, signer) + } + + pub fn limit_order( + &self, + signer: &SigningAccount, + coin_in: Coin, + to_denom: &str, + price: f64, + ) -> RunnerExecuteResult { + #[allow(deprecated)] + let msg = MsgPlaceLimitOrder { + creator: signer.address().to_string(), + receiver: signer.address().to_string(), + token_in: coin_in.denom.clone(), + token_out: to_denom.to_string(), + tick_index_in_to_out: 0, + amount_in: coin_in.amount.to_string(), + order_type: 0, + expiration_time: None, + max_amount_out: None, + limit_sell_price: Some((price * 1e27).to_string()), + min_average_sell_price: None, + }; + self.dex.place_limit_order(msg, signer) + } } fn find_attribute(events: &[Event], key: &str) -> Option { diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs index 71e9a8716..5299d41ee 100644 --- a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs @@ -2,10 +2,13 @@ #![cfg(feature = "test-tube")] #![allow(dead_code)] -use astroport::asset::AssetInfoExt; use cosmwasm_std::{coin, Decimal, Uint128}; +use itertools::Itertools; +use neutron_test_tube::cosmrs::proto::cosmos::bank::v1beta1::QueryAllBalancesRequest; use neutron_test_tube::{Account, NeutronTestApp}; +use astroport::asset::AssetInfoExt; +use astroport::pair_concentrated::ConcentratedPoolParams; use astroport::pair_concentrated_duality::OrderbookConfig; use astroport_test::coins::TestCoin; use common::{ @@ -24,12 +27,15 @@ fn init_on_duality() { let astroport = AstroportHelper::new( neutron, test_coins.clone(), - common_pcl_params(), + ConcentratedPoolParams { + price_scale: Decimal::percent(50), + ..common_pcl_params() + }, OrderbookConfig { enable: true, executor: Some(owner), - liquidity_percent: Decimal::percent(20), - orders_number: 5, + liquidity_percent: Decimal::percent(5), + orders_number: 1, min_asset_0_order_size: Uint128::from(1_000u128), min_asset_1_order_size: Uint128::from(1_000u128), }, @@ -40,18 +46,138 @@ fn init_on_duality() { .helper .app .init_account(&[ - coin(10_000_0000_000000, "untrn"), - coin(1_000_0000_000000, "astro"), + coin(2_000_000_000000u128, "untrn"), + coin(505_000_000000u128, "astro"), ]) .unwrap(); + let initial_balances = [ + astroport.assets[&test_coins[1]].with_balance(500_000_000000u128), + astroport.assets[&test_coins[0]].with_balance(1_000_000_000000u128), + ]; + astroport - .provide_liquidity( - &user, - &[ - astroport.assets[&test_coins[0]].with_balance(1_000_000_000000u128), - astroport.assets[&test_coins[1]].with_balance(1_000_000_000000u128), - ], - ) + .provide_liquidity(&user, &initial_balances) .unwrap(); + + let balances = astroport + .pool_balances() + .unwrap() + .assets + .into_iter() + .sorted_by(|a, b| a.info.to_string().cmp(&b.info.to_string())) + .collect_vec(); + + assert_eq!(balances, initial_balances); + + // let orders = astroport + // .helper + // .list_orders(astroport.pair_addr.as_str()) + // .unwrap() + // .limit_orders + // .into_iter() + // .map(|order| order.tranche_key) + // .collect_vec(); + // dbg!(orders); + // let total_liquidity = astroport + // .helper + // .query_total_ob_liquidity(astroport.pair_addr.as_str()) + // .unwrap(); + // dbg!(total_liquidity); + + let swap_asset = astroport.assets[&test_coins[1]].with_balance(1_000_000000u128); + astroport.swap(&user, &swap_asset, None).unwrap(); + + dbg!(astroport.pool_balances().unwrap()); + + // let orders = astroport + // .helper + // .list_orders(astroport.pair_addr.as_str()) + // .unwrap() + // .limit_orders + // .into_iter() + // .map(|order| order.tranche_key) + // .collect_vec(); + // dbg!(orders); + + let dex_trader = astroport + .helper + .app + .init_account(&[ + coin(10_000_000000u128, "untrn"), + coin(10_000_000000u128, "astro"), + ]) + .unwrap(); + + // let orders = astroport + // .helper + // .list_orders(astroport.pair_addr.as_str()) + // .unwrap(); + // dbg!(orders); + + astroport + .helper + .swap_on_dex(&dex_trader, coin(1_000_000000u128, "untrn"), "astro", 0.49) + .unwrap(); + + // let bal = astroport + // .helper + // .bank + // .query_all_balances(&QueryAllBalancesRequest { + // address: dex_trader.address(), + // pagination: None, + // }) + // .unwrap(); + // dbg!(bal); + + dbg!(astroport.pool_balances().unwrap()); + + let swap_asset = astroport.assets[&test_coins[0]].with_balance(1_000_000000u128); + astroport.swap_max_spread(&user, &swap_asset).unwrap(); + + astroport + .helper + .swap_on_dex(&dex_trader, coin(500_000000u128, "astro"), "untrn", 1.9) + .unwrap(); + + let swap_asset = astroport.assets[&test_coins[1]].with_balance(500_000000u128); + astroport.swap_max_spread(&user, &swap_asset).unwrap(); + + dbg!(astroport.pool_balances().unwrap()); + + let orders = astroport + .helper + .list_orders(astroport.pair_addr.as_str()) + .unwrap(); + dbg!(orders); + + // Creating a huge limit order which should be partially consumed by Astroport pair + let whale = astroport + .helper + .app + .init_account(&[coin(2_000_000_000000u128, "untrn")]) + .unwrap(); + astroport + .helper + .limit_order(&whale, coin(1_000_000_000000u128, "untrn"), "astro", 0.3) + .unwrap(); + + astroport.sync_orders(&astroport.helper.signer).unwrap(); + + let orders = astroport + .helper + .list_orders(astroport.pair_addr.as_str()) + .unwrap(); + dbg!(orders); + + let swap_asset = astroport.assets[&test_coins[0]].with_balance(1000_000000u128); + astroport.swap_max_spread(&user, &swap_asset).unwrap(); + + let orders = astroport + .helper + .list_orders(astroport.pair_addr.as_str()) + .unwrap(); + dbg!(orders); + + panic!("Test panic") } diff --git a/packages/astroport_pcl_common/src/utils.rs b/packages/astroport_pcl_common/src/utils.rs index 8231d9532..76273dc54 100644 --- a/packages/astroport_pcl_common/src/utils.rs +++ b/packages/astroport_pcl_common/src/utils.rs @@ -286,9 +286,9 @@ pub fn compute_swap( // Derive spread using oracle price let spread_fee = if ask_ind == 1 { dy /= config.pool_state.price_state.price_scale; - (offer_amount / config.pool_state.price_state.oracle_price).saturating_sub(dy) + (offer_amount * config.pool_state.price_state.oracle_price).saturating_sub(dy) } else { - offer_amount.saturating_sub(dy / config.pool_state.price_state.oracle_price) + (offer_amount / config.pool_state.price_state.oracle_price).saturating_sub(dy) }; let fee_rate = config.pool_params.fee(&ixs); From e2ac5f335d14f77657b30e13dffcf5e00d30e7b3 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Wed, 29 Jan 2025 14:25:27 +0300 Subject: [PATCH 07/24] WIP: processing possible filled orders in reply --- .../pair_concentrated_duality/src/execute.rs | 91 +++++++------- .../src/orderbook/custom_types.rs | 3 +- .../src/orderbook/execute.rs | 20 ++- .../src/orderbook/state.rs | 116 +++++------------- .../src/orderbook/utils.rs | 116 +++++++++++++++++- .../pair_concentrated_duality/src/queries.rs | 105 +++++++--------- .../pair_concentrated_duality/src/reply.rs | 53 +++++++- .../pair_concentrated_duality/src/utils.rs | 83 +------------ .../tests/common/astroport_wrapper.rs | 25 +++- .../tests/common/helper.rs | 1 - .../tests/pcl_duality_e2e.rs | 21 ++-- .../src/pair_concentrated_duality.rs | 2 - 12 files changed, 323 insertions(+), 313 deletions(-) diff --git a/contracts/pair_concentrated_duality/src/execute.rs b/contracts/pair_concentrated_duality/src/execute.rs index 8a087421b..cfe1ccf02 100644 --- a/contracts/pair_concentrated_duality/src/execute.rs +++ b/contracts/pair_concentrated_duality/src/execute.rs @@ -28,11 +28,9 @@ use crate::error::ContractError; use crate::instantiate::LP_TOKEN_PRECISION; use crate::orderbook::execute::{process_cumulative_trade, sync_pool_with_orderbook}; use crate::orderbook::state::OrderbookState; +use crate::orderbook::utils::{fetch_cumulative_trade, Liquidity}; use crate::state::{CONFIG, OWNERSHIP_PROPOSAL}; -use crate::utils::{ - calculate_shares, ensure_min_assets_to_receive, get_assets_with_precision, - query_contract_balances, query_pools, -}; +use crate::utils::{calculate_shares, ensure_min_assets_to_receive, get_assets_with_precision}; #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( @@ -166,17 +164,13 @@ pub fn provide_liquidity( let mut ob_state = OrderbookState::load(deps.storage)?; - // This call fetches possible cumulative trade and caches orderbook balances in ob_state + let liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; + + // This call fetches possible cumulative trade let maybe_cumulative_trade = - ob_state.fetch_cumulative_trade(deps.as_ref(), &env.contract.address, &precisions)?; + fetch_cumulative_trade(&precisions, &ob_state.last_balances, &liquidity.orderbook)?; - let mut pools = query_pools( - deps.querier, - &env.contract.address, - &config, - &precisions, - &ob_state, - )?; + let mut pools = liquidity.total_dec(&precisions)?; let old_real_price = config.pool_state.price_state.last_price; @@ -272,8 +266,11 @@ pub fn provide_liquidity( CONFIG.save(deps.storage, &config)?; - let submsgs = - ob_state.flatten_msgs_and_add_callback(&[cancel_msgs, order_msgs, mint_lp_messages]); + let submsgs = ob_state.flatten_msgs_and_add_callback( + &liquidity, + &[cancel_msgs, mint_lp_messages], + order_msgs, + ); ob_state.save(deps.storage)?; Ok(response.add_submessages(submsgs).add_attributes([ @@ -308,17 +305,13 @@ fn withdraw_liquidity( let precisions = Precisions::new(deps.storage)?; - // This call fetches possible cumulative trade and caches orderbook balances in ob_state + let mut liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; + + // This call fetches possible cumulative trade let maybe_cumulative_trade = - ob_state.fetch_cumulative_trade(deps.as_ref(), &env.contract.address, &precisions)?; + fetch_cumulative_trade(&precisions, &ob_state.last_balances, &liquidity.orderbook)?; - let mut pools = query_pools( - deps.querier, - &env.contract.address, - &config, - &precisions, - &ob_state, - )?; + let mut pools = liquidity.total_dec(&precisions)?; let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)?; @@ -360,17 +353,6 @@ fn withdraw_liquidity( xs[0] -= refund_assets[0].amount; xs[1] -= refund_assets[1].amount; - let contract_balances = - query_contract_balances(deps.querier, &env.contract.address, &config, &precisions)?; - // If the contract does not have enough liquidity - do not deploy new orders - let enough_liq_cond = refund_assets[0].amount <= contract_balances[0].amount - && refund_assets[1].amount <= contract_balances[1].amount; - let order_msgs = if enough_liq_cond { - ob_state.deploy_orders(&env, &config, &xs, &precisions, deps.api)? - } else { - vec![] - }; - // decrease XCP xs[1] *= config.pool_state.price_state.price_scale; let amp_gamma = config.pool_state.get_amp_gamma(&env); @@ -381,12 +363,16 @@ fn withdraw_liquidity( let refund_assets = refund_assets .into_iter() - .map(|asset| { + .enumerate() + .map(|(ind, asset)| { let prec = precisions.get_precision(&asset.info).unwrap(); + let amount = asset.amount.to_uint(prec)?; + + liquidity.contract[ind].amount -= amount; Ok(Asset { info: asset.info, - amount: asset.amount.to_uint(prec)?, + amount, }) }) .collect::>>()?; @@ -399,14 +385,18 @@ fn withdraw_liquidity( .map(|asset| asset.into_msg(&info.sender)) .collect::>>()?; withdraw_messages.push(tf_burn_msg( - env.contract.address, + &env.contract.address, coin(amount.u128(), &config.pair_info.liquidity_token), )); CONFIG.save(deps.storage, &config)?; - let submsgs = - ob_state.flatten_msgs_and_add_callback(&[cancel_msgs, withdraw_messages, order_msgs]); + let order_msgs = ob_state.deploy_orders(&env, &config, &xs, &precisions, deps.api)?; + let submsgs = ob_state.flatten_msgs_and_add_callback( + &liquidity, + &[cancel_msgs, withdraw_messages], + order_msgs, + ); ob_state.save(deps.storage)?; Ok(response.add_submessages(submsgs).add_attributes([ @@ -445,21 +435,17 @@ fn swap( let mut ob_state = OrderbookState::load(deps.storage)?; - // This call fetches possible cumulative trade and caches orderbook balances in ob_state + let mut liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; + + // This call fetches possible cumulative trade let maybe_cumulative_trade = - ob_state.fetch_cumulative_trade(deps.as_ref(), &env.contract.address, &precisions)?; + fetch_cumulative_trade(&precisions, &ob_state.last_balances, &liquidity.orderbook)?; // TODO: delete me deps.api .debug(&format!("swap: {:?}", maybe_cumulative_trade)); - let mut pools = query_pools( - deps.querier, - &env.contract.address, - &config, - &precisions, - &ob_state, - )?; + let mut pools = liquidity.total_dec(&precisions)?; let (offer_ind, _) = pools .iter() @@ -559,6 +545,8 @@ fn swap( .with_balance(return_amount) .into_msg(&receiver)?]; + liquidity.contract[ask_ind].amount -= return_amount; + // Send the shared fee let mut fee_share_amount = Uint128::zero(); if let Some(fee_share) = &config.fee_share { @@ -566,6 +554,7 @@ fn swap( if !fee_share_amount.is_zero() { let fee = pools[ask_ind].info.with_balance(fee_share_amount); messages.push(fee.into_msg(&fee_share.recipient)?); + liquidity.contract[ask_ind].amount -= fee_share_amount; } } @@ -576,6 +565,7 @@ fn swap( if !maker_fee.is_zero() { let fee = pools[ask_ind].info.with_balance(maker_fee); messages.push(fee.into_msg(fee_address)?); + liquidity.contract[ask_ind].amount -= maker_fee; } } @@ -587,7 +577,8 @@ fn swap( CONFIG.save(deps.storage, &config)?; - let submsgs = ob_state.flatten_msgs_and_add_callback(&[cancel_msgs, messages, order_msgs]); + let submsgs = + ob_state.flatten_msgs_and_add_callback(&liquidity, &[cancel_msgs, messages], order_msgs); ob_state.save(deps.storage)?; diff --git a/contracts/pair_concentrated_duality/src/orderbook/custom_types.rs b/contracts/pair_concentrated_duality/src/orderbook/custom_types.rs index a0abd4949..b5a31e44d 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/custom_types.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/custom_types.rs @@ -1,4 +1,3 @@ -use neutron_std::types::cosmos::base::query::v1beta1::PageResponse; use serde::Deserialize; // !!! Workaround which fixes an invalid type in the original neutron-std crate @@ -17,5 +16,5 @@ pub struct CustomLimitOrderTrancheUser { #[derive(Deserialize)] pub struct CustomQueryAllLimitOrderTrancheUserByAddressResponse { pub limit_orders: Vec, - pub pagination: Option, + // pub pagination: Option, } diff --git a/contracts/pair_concentrated_duality/src/orderbook/execute.rs b/contracts/pair_concentrated_duality/src/orderbook/execute.rs index ce5b3e3a3..f47eeacc1 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/execute.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/execute.rs @@ -13,14 +13,14 @@ use astroport_pcl_common::utils::accumulate_prices; use crate::error::ContractError; use crate::instantiate::LP_TOKEN_PRECISION; +use crate::orderbook::utils::{fetch_cumulative_trade, Liquidity}; use crate::state::CONFIG; -use crate::utils::query_pools; use super::error::OrderbookError; use super::state::OrderbookState; /// CumulativeTrade represents all trades that happened on orderbook as one trade. -/// I.e., swap from base_asset -> output_asset. +/// I.e., swap from base_asset -> quote_asset. /// In this context, Astroport always charges protocol fees from quote asset. #[cw_serde] pub struct CumulativeTrade { @@ -140,22 +140,17 @@ pub fn sync_pool_with_orderbook( } let precisions = Precisions::new(deps.storage)?; + let mut config = CONFIG.load(deps.storage)?; + let liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; if let Some(cumulative_trade) = - ob_state.fetch_cumulative_trade(deps.as_ref(), &env.contract.address, &precisions)? + fetch_cumulative_trade(&precisions, &ob_state.last_balances, &liquidity.orderbook)? { deps.api.debug(&format!( "Syncing pool with orderbook: {:?}", &cumulative_trade )); - let mut config = CONFIG.load(deps.storage)?; - let mut pools = query_pools( - deps.querier, - &env.contract.address, - &config, - &precisions, - &ob_state, - )?; + let mut pools = liquidity.total_dec(&precisions)?; let mut balances = pools .iter_mut() .map(|asset| &mut asset.amount) @@ -178,7 +173,8 @@ pub fn sync_pool_with_orderbook( let balances = pools.iter().map(|asset| asset.amount).collect_vec(); let order_msgs = ob_state.deploy_orders(&env, &config, &balances, &precisions, deps.api)?; - let submsgs = ob_state.flatten_msgs_and_add_callback(&[cancel_msgs, order_msgs]); + let submsgs = + ob_state.flatten_msgs_and_add_callback(&liquidity, &[cancel_msgs], order_msgs); Ok(response .add_attributes([ diff --git a/contracts/pair_concentrated_duality/src/orderbook/state.rs b/contracts/pair_concentrated_duality/src/orderbook/state.rs index 440ba4080..f4f0d97d3 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/state.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/state.rs @@ -1,21 +1,18 @@ -use std::cmp::Ordering; - use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - attr, coin, ensure, from_json, to_json_vec, Addr, Api, Attribute, Coin, CosmosMsg, Decimal, - Decimal256, Deps, Empty, Env, QuerierWrapper, QueryRequest, ReplyOn, StdError, StdResult, - Storage, SubMsg, Uint128, + attr, ensure, from_json, to_json_vec, Addr, Api, Attribute, CosmosMsg, Decimal, Decimal256, + Deps, Empty, Env, QuerierWrapper, QueryRequest, ReplyOn, StdError, StdResult, Storage, SubMsg, + Uint128, }; use cw_storage_plus::Item; use itertools::Itertools; -use neutron_std::types::cosmos::base::query::v1beta1::{PageRequest, PageResponse}; +use neutron_std::types::cosmos::base::query::v1beta1::PageRequest; use neutron_std::types::neutron::dex::{ DexQuerier, MsgCancelLimitOrder, MsgCancelLimitOrderResponse, QueryAllLimitOrderTrancheUserByAddressRequest, }; -use serde::Deserialize; -use astroport::asset::{Asset, AssetInfo, AssetInfoExt, Decimal256Ext, DecimalAsset}; +use astroport::asset::{Asset, Decimal256Ext}; use astroport::cosmwasm_ext::IntegerToDecimal; use astroport::pair_concentrated_duality::UpdateDualityOrderbook; use astroport::pair_concentrated_duality::{OrderbookConfig, ReplyIds}; @@ -25,8 +22,7 @@ use astroport_pcl_common::state::{Config, Precisions}; use crate::error::ContractError; use crate::orderbook::consts::{MAX_LIQUIDITY_PERCENT, MIN_LIQUIDITY_PERCENT, ORDER_SIZE_LIMITS}; use crate::orderbook::custom_types::CustomQueryAllLimitOrderTrancheUserByAddressResponse; -use crate::orderbook::execute::CumulativeTrade; -use crate::orderbook::utils::{compute_swap, SpotOrdersFactory}; +use crate::orderbook::utils::{compute_swap, Liquidity, SpotOrdersFactory}; macro_rules! validate_param { ($name:ident, $val:expr, $min:expr, $max:expr) => { @@ -58,9 +54,11 @@ pub struct OrderbookState { /// Array with tranche keys of all posted orders. pub orders: Vec, /// Last recorded balances on the orderbook. - pub last_balances: Vec, + pub last_balances: Vec, /// Whether the orderbook integration enabled or not. pub enabled: bool, + /// Snapshot of total balances before entering reply + pub pre_reply_balances: Vec, } const OB_CONFIG: Item = Item::new("orderbook_config"); @@ -74,8 +72,9 @@ impl OrderbookState { liquidity_percent: orderbook_config.liquidity_percent, orders: vec![], last_balances: vec![], - enabled: orderbook_config.enable, + enabled: false, executor: orderbook_config.executor.map(Addr::unchecked), + pre_reply_balances: vec![], }; config.validate(api)?; @@ -194,7 +193,7 @@ impl OrderbookState { querier: QuerierWrapper, addr: &Addr, force_update: bool, - ) -> StdResult> { + ) -> StdResult> { if !force_update && self.last_balances.is_empty() { Ok(vec![]) } else { @@ -233,29 +232,12 @@ impl OrderbookState { .map(|proto_coin| proto_coin.amount.parse()) .try_collect()?; let amount: Uint128 = amounts.iter().sum(); - Ok(coin(amount.u128(), denom)) + Ok(Asset::native(denom, amount)) }) .collect() } } - /// Convert orderbook balances into DecimalAsset. - /// It is required that self.last_balances is updated before this method. - pub fn query_ob_liquidity_dec( - &self, - precisions: &Precisions, - ) -> Result, ContractError> { - self.last_balances - .iter() - .map(|coin| { - let asset = Asset::native(&coin.denom, coin.amount); - asset - .to_decimal_asset(precisions.get_precision(&asset.info)?) - .map_err(Into::into) - }) - .collect() - } - /// Fetch all orders and save their tranche keys in the state. pub fn fetch_all_orders(&mut self, deps: Deps, addr: &Addr) -> StdResult<()> { let query_msg = to_json_vec(&QueryRequest::::Stargate { @@ -326,57 +308,6 @@ impl OrderbookState { .collect() } - /// Fetch orderbook and check whether any of the orders have been executed. - /// Return CumulativeTrade object which is the difference between last and current balances. - /// Cache new balances in the state. - pub fn fetch_cumulative_trade( - &mut self, - deps: Deps, - addr: &Addr, - precisions: &Precisions, - ) -> Result, ContractError> { - let mut new_balances = self.query_ob_liquidity(deps.querier, addr, false)?; - if !new_balances.is_empty() { - if self.last_balances[0].denom != new_balances[0].denom { - new_balances.swap(0, 1); - } - - let bal_diffs = self - .last_balances - .iter() - .zip(new_balances.iter()) - .map(|(a, b)| b.amount.abs_diff(a.amount)) - .collect_vec(); - - let diff_to_dec_asset = |ind: usize| -> Result<_, ContractError> { - let asset_info = AssetInfo::native(&new_balances[ind].denom); - let precision = precisions.get_precision(&asset_info)?; - Ok(asset_info.with_dec_balance(bal_diffs[ind].to_decimal256(precision)?)) - }; - - let maybe_trade = match self.last_balances[0].amount.cmp(&new_balances[0].amount) { - // We sold asset 0 for asset 1 - Ordering::Less => Some(CumulativeTrade { - base_asset: diff_to_dec_asset(1)?, - quote_asset: diff_to_dec_asset(0)?, - }), - // We bought asset 0 with asset 1 - Ordering::Greater => Some(CumulativeTrade { - base_asset: diff_to_dec_asset(0)?, - quote_asset: diff_to_dec_asset(1)?, - }), - // No trade happened - Ordering::Equal => None, - }; - - self.last_balances = new_balances; - - Ok(maybe_trade) - } else { - Ok(None) - } - } - /// Construct an array with new orders. /// Return an empty array if orderbook integration is disabled. // TODO: remove api from the arguments @@ -474,14 +405,27 @@ impl OrderbookState { /// Flatten all messages into one vector and add a callback to the last message only /// if orderbook integration is enabled. - pub fn flatten_msgs_and_add_callback(&self, messages: &[Vec]) -> Vec { - let mut submsgs = messages.concat().into_iter().map(SubMsg::new).collect_vec(); - - if let (true, Some(last)) = (self.enabled, submsgs.last_mut()) { + pub fn flatten_msgs_and_add_callback( + &mut self, + liquidity: &Liquidity, + messages: &[Vec], + order_msgs: Vec, + ) -> Vec { + let is_empty_order_msgs = order_msgs.is_empty(); + let mut submsgs = messages + .concat() + .into_iter() + .chain(order_msgs) + .map(SubMsg::new) + .collect_vec(); + + if let (true, false, Some(last)) = (self.enabled, is_empty_order_msgs, submsgs.last_mut()) { last.id = ReplyIds::PostLimitOrderCb as u64; last.reply_on = ReplyOn::Success; } + self.pre_reply_balances = liquidity.total(); + submsgs } } diff --git a/contracts/pair_concentrated_duality/src/orderbook/utils.rs b/contracts/pair_concentrated_duality/src/orderbook/utils.rs index e904e7362..c5346e0ab 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/utils.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/utils.rs @@ -1,14 +1,23 @@ use std::cmp::Ordering; -use cosmwasm_std::{Addr, Api, CosmosMsg, Decimal256, OverflowError, StdResult, Uint256}; +use cosmwasm_std::{ + Addr, Api, CosmosMsg, Decimal256, OverflowError, QuerierWrapper, StdResult, Uint128, Uint256, +}; +use itertools::Itertools; use neutron_std::types::neutron::dex::MsgPlaceLimitOrder; -use astroport::asset::{AssetInfo, Decimal256Ext}; +use astroport::asset::{Asset, AssetInfo, AssetInfoExt, Decimal256Ext, DecimalAsset}; +use astroport::cosmwasm_ext::IntegerToDecimal; +use astroport_pcl_common::state::Precisions; use astroport_pcl_common::{ calc_y, state::{AmpGamma, Config}, }; +use crate::error::ContractError; +use crate::orderbook::execute::CumulativeTrade; +use crate::orderbook::state::OrderbookState; + /// Calculate the swap result using cached D. pub fn compute_swap( ixs: &[Decimal256], @@ -206,6 +215,109 @@ fn price_to_duality_notation( Ok(price) } +#[derive(Debug)] +pub struct Liquidity { + pub contract: Vec, + pub orderbook: Vec, +} + +impl Liquidity { + pub fn new( + querier: QuerierWrapper, + config: &Config, + ob_state: &OrderbookState, + force_update: bool, + ) -> StdResult { + Ok(Self { + contract: config + .pair_info + .query_pools(&querier, &config.pair_info.contract_addr)?, + orderbook: ob_state + .query_ob_liquidity(querier, &config.pair_info.contract_addr, force_update)? + .into_iter() + .map(Asset::from) + .collect(), + }) + } + + pub fn total(&self) -> Vec { + let mut balances = self + .contract + .iter() + .chain(self.orderbook.iter()) + .into_group_map_by(|asset| asset.info.clone()) + .into_iter() + .map(|(info, assets)| { + let sum = assets.iter().fold(Uint128::zero(), |acc, a| acc + a.amount); + info.with_balance(sum) + }) + .collect_vec(); + + if balances[0].info != self.contract[0].info { + balances.swap(0, 1); + } + + balances + } + + pub fn total_dec(&self, precisions: &Precisions) -> Result, ContractError> { + self.total() + .into_iter() + .map(|asset| { + asset + .to_decimal_asset(precisions.get_precision(&asset.info)?) + .map_err(Into::into) + }) + .collect() + } +} + +/// Checking whether there is a difference between the last and current balances. +/// Return CumulativeTrade object which is the difference between last and current balances. +pub fn fetch_cumulative_trade( + precisions: &Precisions, + last_balances: &[Asset], + new_balances: &[Asset], +) -> Result, ContractError> { + let mut new_balances = new_balances.to_vec(); + if !new_balances.is_empty() { + if last_balances[0].info != new_balances[0].info { + new_balances.swap(0, 1); + } + + let bal_diffs = last_balances + .iter() + .zip(new_balances.iter()) + .map(|(a, b)| b.amount.abs_diff(a.amount)) + .collect_vec(); + + let diff_to_dec_asset = |ind: usize| -> Result<_, ContractError> { + let asset_info = &new_balances[ind].info; + let precision = precisions.get_precision(asset_info)?; + Ok(asset_info.with_dec_balance(bal_diffs[ind].to_decimal256(precision)?)) + }; + + let maybe_trade = match last_balances[0].amount.cmp(&new_balances[0].amount) { + // We sold asset 0 for asset 1 + Ordering::Less => Some(CumulativeTrade { + base_asset: diff_to_dec_asset(1)?, + quote_asset: diff_to_dec_asset(0)?, + }), + // We bought asset 0 with asset 1 + Ordering::Greater => Some(CumulativeTrade { + base_asset: diff_to_dec_asset(0)?, + quote_asset: diff_to_dec_asset(1)?, + }), + // No trade happened + Ordering::Equal => None, + }; + + Ok(maybe_trade) + } else { + Ok(None) + } +} + // TODO: fix tests // #[cfg(test)] // mod unit_tests { diff --git a/contracts/pair_concentrated_duality/src/queries.rs b/contracts/pair_concentrated_duality/src/queries.rs index d66604856..1aa422efe 100644 --- a/contracts/pair_concentrated_duality/src/queries.rs +++ b/contracts/pair_concentrated_duality/src/queries.rs @@ -1,3 +1,9 @@ +use cosmwasm_std::{ + to_json_binary, Binary, Decimal, Decimal256, DecimalRangeExceeded, Deps, Env, StdError, + StdResult, Uint128, +}; +use itertools::Itertools; + use astroport::asset::Asset; use astroport::cosmwasm_ext::{DecimalToInteger, IntegerToDecimal}; use astroport::pair::{ @@ -12,17 +18,13 @@ use astroport_pcl_common::utils::{ get_share_in_assets, }; use astroport_pcl_common::{calc_d, get_xcp}; -use cosmwasm_std::{ - to_json_binary, Binary, Decimal, Decimal256, DecimalRangeExceeded, Deps, Env, StdError, - StdResult, Uint128, -}; -use itertools::Itertools; use crate::error::ContractError; use crate::instantiate::LP_TOKEN_PRECISION; use crate::orderbook::state::OrderbookState; +use crate::orderbook::utils::Liquidity; use crate::state::CONFIG; -use crate::utils::{calculate_shares, get_assets_with_precision, pool_info, query_pools}; +use crate::utils::{calculate_shares, get_assets_with_precision, pool_info}; /// Exposes all the queries available in the contract. /// @@ -109,14 +111,10 @@ fn query_share(deps: Deps, amount: Uint128) -> StdResult> { let precisions = Precisions::new(deps.storage)?; let ob_state = OrderbookState::load(deps.storage)?; - let pools = query_pools( - deps.querier, - &config.pair_info.contract_addr, - &config, - &precisions, - &ob_state, - ) - .map_err(|err| StdError::generic_err(err.to_string()))?; + let liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; + let pools = liquidity + .total_dec(&precisions) + .map_err(|e| StdError::generic_err(e.to_string()))?; let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)?; let refund_assets = get_share_in_assets(&pools, amount.saturating_sub(Uint128::one()), total_share); @@ -147,13 +145,10 @@ pub fn query_simulation( let ob_state = OrderbookState::load(deps.storage)?; - let pools = query_pools( - deps.querier, - &env.contract.address, - &config, - &precisions, - &ob_state, - )?; + let liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; + let pools = liquidity + .total_dec(&precisions) + .map_err(|e| StdError::generic_err(e.to_string()))?; let (offer_ind, _) = pools .iter() @@ -212,13 +207,10 @@ pub fn query_reverse_simulation( let ob_state = OrderbookState::load(deps.storage)?; - let pools = query_pools( - deps.querier, - &env.contract.address, - &config, - &precisions, - &ob_state, - )?; + let liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; + let pools = liquidity + .total_dec(&precisions) + .map_err(|e| StdError::generic_err(e.to_string()))?; let (ask_ind, _) = pools .iter() @@ -247,13 +239,10 @@ fn query_cumulative_prices( let precisions = Precisions::new(deps.storage)?; let ob_state = OrderbookState::load(deps.storage)?; - let pools = query_pools( - deps.querier, - &env.contract.address, - &config, - &precisions, - &ob_state, - )?; + let liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; + let pools = liquidity + .total_dec(&precisions) + .map_err(|e| StdError::generic_err(e.to_string()))?; let xs = pools.iter().map(|asset| asset.amount).collect_vec(); let last_real_price = calc_last_prices(&xs, &config, &env)?; @@ -279,17 +268,13 @@ pub fn query_lp_price(deps: Deps, env: Env) -> StdResult { if !total_lp.is_zero() { let precisions = Precisions::new(deps.storage)?; - let mut ixs = query_pools( - deps.querier, - &env.contract.address, - &config, - &precisions, - &ob_state, - ) - .map_err(|err| StdError::generic_err(err.to_string()))? - .into_iter() - .map(|asset| asset.amount) - .collect_vec(); + + let liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; + let pools = liquidity + .total_dec(&precisions) + .map_err(|e| StdError::generic_err(e.to_string()))?; + + let mut ixs = pools.into_iter().map(|asset| asset.amount).collect_vec(); ixs[1] *= config.pool_state.price_state.price_scale; let amp_gamma = config.pool_state.get_amp_gamma(&env); let d = calc_d(&ixs, &_gamma)?; @@ -342,17 +327,12 @@ pub fn query_compute_d(deps: Deps, env: Env) -> StdResult { let ob_state = OrderbookState::load(deps.storage)?; - let mut xs = query_pools( - deps.querier, - &env.contract.address, - &config, - &precisions, - &ob_state, - ) - .map_err(|e| StdError::generic_err(e.to_string()))? - .into_iter() - .map(|a| a.amount) - .collect_vec(); + let liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; + let pools = liquidity + .total_dec(&precisions) + .map_err(|e| StdError::generic_err(e.to_string()))?; + + let mut xs = pools.into_iter().map(|a| a.amount).collect_vec(); if xs[0].is_zero() || xs[1].is_zero() { return Err(StdError::generic_err("Pools are empty")); @@ -379,13 +359,10 @@ pub fn query_simulate_provide( let ob_state = OrderbookState::load(deps.storage)?; - let pools = query_pools( - deps.querier, - &env.contract.address, - &config, - &precisions, - &ob_state, - )?; + let liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; + let pools = liquidity + .total_dec(&precisions) + .map_err(|e| StdError::generic_err(e.to_string()))?; let deposits = get_assets_with_precision(deps, &config, &mut assets, &pools, &precisions)?; diff --git a/contracts/pair_concentrated_duality/src/reply.rs b/contracts/pair_concentrated_duality/src/reply.rs index d4783c7e0..fbc6f29a3 100644 --- a/contracts/pair_concentrated_duality/src/reply.rs +++ b/contracts/pair_concentrated_duality/src/reply.rs @@ -3,9 +3,12 @@ use itertools::Itertools; use astroport::pair_concentrated_duality::ReplyIds; use astroport::token_factory::MsgCreateDenomResponse; +use astroport_pcl_common::state::Precisions; use crate::error::ContractError; +use crate::orderbook::execute::process_cumulative_trade; use crate::orderbook::state::OrderbookState; +use crate::orderbook::utils::{fetch_cumulative_trade, Liquidity}; use crate::state::CONFIG; /// The entry point to the contract for processing replies from submessages. @@ -40,13 +43,53 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result Result, ContractError> { - config - .pair_info - .query_pools(&querier, addr)? - .into_iter() - .map(|asset| { - asset - .to_decimal_asset(precisions.get_precision(&asset.info)?) - .map_err(Into::into) - }) - .collect() -} - -/// Returns current pool's reserves where amount is in [`Decimal256`] form. -/// Query contract balances and orderbook liquidity and merge them. -pub(crate) fn query_pools( - querier: QuerierWrapper, - addr: &Addr, - config: &Config, - precisions: &Precisions, - ob_state: &OrderbookState, -) -> Result, ContractError> { - let contract_liq = query_contract_balances(querier, addr, config, precisions)?; - let ob_liquidity = ob_state.query_ob_liquidity_dec(precisions)?; - - let mut balances = contract_liq - .iter() - .chain(ob_liquidity.iter()) - .into_group_map_by(|asset| asset.info.clone()) - .into_iter() - .map(|(info, assets)| { - let sum = assets - .iter() - .fold(Decimal256::zero(), |acc, a| acc + a.amount); - Ok(info.with_dec_balance(sum)) - }) - .collect::>>()?; - - if balances[0].info != config.pair_info.asset_infos[0] { - balances.swap(0, 1); - } - - Ok(balances) -} +use crate::orderbook::utils::Liquidity; /// Returns the total amount of assets in the pool as well as the total amount of LP tokens currently minted. pub(crate) fn pool_info( @@ -75,30 +24,8 @@ pub(crate) fn pool_info( config: &Config, ob_state: &OrderbookState, ) -> StdResult<(Vec, Uint128)> { - let contract_liq = config - .pair_info - .query_pools(&querier, &config.pair_info.contract_addr)?; - let ob_liquidity = ob_state - .query_ob_liquidity(querier, &config.pair_info.contract_addr, false)? - .into_iter() - .map(|coin| Asset::native(coin.denom, coin.amount)) - .collect_vec(); - // Merge contract and orderbook liquidity - let mut balances = contract_liq - .iter() - .chain(ob_liquidity.iter()) - .into_group_map_by(|asset| asset.info.clone()) - .into_iter() - .map(|(info, assets)| { - let amount = assets.iter().fold(Uint128::zero(), |acc, a| acc + a.amount); - Ok(Asset { info, amount }) - }) - .collect::>>()?; - - if balances[0].info != config.pair_info.asset_infos[0] { - balances.swap(0, 1); - } - + let liquidity = Liquidity::new(querier, config, ob_state, false)?; + let balances = liquidity.total(); let total_share = query_native_supply(&querier, &config.pair_info.liquidity_token)?; Ok((balances, total_share)) diff --git a/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs b/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs index 1bc13519f..aaa0a20f7 100644 --- a/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs +++ b/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs @@ -11,7 +11,7 @@ use neutron_test_tube::cosmrs::proto::cosmwasm::wasm::v1::MsgExecuteContractResp use neutron_test_tube::{Account, ExecuteResponse, SigningAccount}; use astroport::pair::PoolResponse; -use astroport::pair_concentrated_duality::DualityPairMsg; +use astroport::pair_concentrated_duality::{DualityPairMsg, UpdateDualityOrderbook}; use astroport::{ asset::{native_asset_info, Asset, AssetInfo, PairInfo}, factory::{PairConfig, PairType}, @@ -306,4 +306,27 @@ impl<'a> AstroportHelper<'a> { &[], ) } + + pub fn enable_orderbook( + &self, + sender: &SigningAccount, + enable: bool, + ) -> AnyResult> { + self.helper.execute_contract( + sender, + self.pair_addr.as_str(), + &ExecuteMsg::Custom(DualityPairMsg::UpdateOrderbookConfig( + UpdateDualityOrderbook { + enable: Some(enable), + executor: None, + remove_executor: false, + orders_number: None, + min_asset_0_order_size: None, + min_asset_1_order_size: None, + liquidity_percent: None, + }, + )), + &[], + ) + } } diff --git a/contracts/pair_concentrated_duality/tests/common/helper.rs b/contracts/pair_concentrated_duality/tests/common/helper.rs index 5eb59eb7e..65fb97bd1 100644 --- a/contracts/pair_concentrated_duality/tests/common/helper.rs +++ b/contracts/pair_concentrated_duality/tests/common/helper.rs @@ -218,7 +218,6 @@ impl Helper { to_json_binary(&ConcentratedDualityParams { main_params: params, orderbook_config: OrderbookConfig { - enable: false, liquidity_percent: Decimal::percent(20), orders_number: 5, min_asset_0_order_size: Uint128::from(1000u128), diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs index 5299d41ee..527bf5ed8 100644 --- a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs @@ -32,7 +32,6 @@ fn init_on_duality() { ..common_pcl_params() }, OrderbookConfig { - enable: true, executor: Some(owner), liquidity_percent: Decimal::percent(5), orders_number: 1, @@ -42,6 +41,8 @@ fn init_on_duality() { ) .unwrap(); + astroport.enable_orderbook(&astroport.owner, true).unwrap(); + let user = astroport .helper .app @@ -90,15 +91,15 @@ fn init_on_duality() { dbg!(astroport.pool_balances().unwrap()); - // let orders = astroport - // .helper - // .list_orders(astroport.pair_addr.as_str()) - // .unwrap() - // .limit_orders - // .into_iter() - // .map(|order| order.tranche_key) - // .collect_vec(); - // dbg!(orders); + let orders = astroport + .helper + .list_orders(astroport.pair_addr.as_str()) + .unwrap() + .limit_orders + .into_iter() + .map(|order| order.tranche_key) + .collect_vec(); + dbg!(orders); let dex_trader = astroport .helper diff --git a/packages/astroport/src/pair_concentrated_duality.rs b/packages/astroport/src/pair_concentrated_duality.rs index ea6d37c1b..8cc610702 100644 --- a/packages/astroport/src/pair_concentrated_duality.rs +++ b/packages/astroport/src/pair_concentrated_duality.rs @@ -5,8 +5,6 @@ use crate::pair_concentrated::ConcentratedPoolParams; #[cw_serde] pub struct OrderbookConfig { - /// Determines whether the orderbook is enabled - pub enable: bool, /// The address of the orderbook sync executor. If None, then the sync is permissionless. pub executor: Option, /// Number of orders on each side of the orderbook From 17dfea26f4cb0e38fa360504044de770106e6331 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Wed, 29 Jan 2025 18:57:51 +0300 Subject: [PATCH 08/24] WIP: fixed math and basic test --- .../src/orderbook/execute.rs | 4 +- .../tests/common/astroport_wrapper.rs | 20 +-- .../tests/common/helper.rs | 2 +- .../tests/common/neutron_wrapper.rs | 14 ++ .../tests/pcl_duality_e2e.rs | 127 ++++++++++-------- packages/astroport_pcl_common/src/utils.rs | 5 +- 6 files changed, 100 insertions(+), 72 deletions(-) diff --git a/contracts/pair_concentrated_duality/src/orderbook/execute.rs b/contracts/pair_concentrated_duality/src/orderbook/execute.rs index f47eeacc1..9e3dab5b0 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/execute.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/execute.rs @@ -98,9 +98,9 @@ pub fn process_cumulative_trade( // especially if token precisions are 18. if trade.base_asset.amount >= MIN_TRADE_SIZE && trade.quote_asset.amount >= MIN_TRADE_SIZE { let last_price = if offer_ind == 0 { - trade.base_asset.amount / trade.quote_asset.amount - } else { trade.quote_asset.amount / trade.base_asset.amount + } else { + trade.base_asset.amount / trade.quote_asset.amount }; let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)? diff --git a/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs b/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs index aaa0a20f7..5d3b5785e 100644 --- a/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs +++ b/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs @@ -251,11 +251,7 @@ impl<'a> AstroportHelper<'a> { &self, sender: &SigningAccount, offer_asset: &Asset, - ) -> AnyResult< - ExecuteResponse< - neutron_test_tube::cosmrs::proto::cosmwasm::wasm::v1::MsgExecuteContractResponse, - >, - > { + ) -> AnyResult> { self.swap(sender, offer_asset, Some(f64_to_dec(0.5))) } @@ -264,11 +260,7 @@ impl<'a> AstroportHelper<'a> { sender: &SigningAccount, offer_asset: &Asset, max_spread: Option, - ) -> AnyResult< - ExecuteResponse< - neutron_test_tube::cosmrs::proto::cosmwasm::wasm::v1::MsgExecuteContractResponse, - >, - > { + ) -> AnyResult> { match &offer_asset.info { AssetInfo::Token { .. } => unimplemented!(), AssetInfo::NativeToken { .. } => { @@ -292,6 +284,14 @@ impl<'a> AstroportHelper<'a> { self.helper .wasm .query(self.pair_addr.as_str(), &pair::QueryMsg::Pool {}) + .map(|res: PoolResponse| PoolResponse { + assets: res + .assets + .into_iter() + .sorted_by(|a, b| a.info.to_string().cmp(&b.info.to_string())) + .collect_vec(), + ..res + }) .map_err(Into::into) } diff --git a/contracts/pair_concentrated_duality/tests/common/helper.rs b/contracts/pair_concentrated_duality/tests/common/helper.rs index 65fb97bd1..fe9c8d37b 100644 --- a/contracts/pair_concentrated_duality/tests/common/helper.rs +++ b/contracts/pair_concentrated_duality/tests/common/helper.rs @@ -40,7 +40,7 @@ pub type ExecuteMsg = ExecuteMsgExt; pub fn common_pcl_params() -> ConcentratedPoolParams { ConcentratedPoolParams { - amp: f64_to_dec(10f64), + amp: f64_to_dec(40f64), gamma: f64_to_dec(0.000145), mid_fee: f64_to_dec(0.0026), out_fee: f64_to_dec(0.0045), diff --git a/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs b/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs index 74c7451b8..0372f3624 100644 --- a/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs +++ b/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs @@ -301,6 +301,20 @@ impl<'a> TestAppWrapper<'a> { }; self.dex.place_limit_order(msg, signer) } + + pub fn cancel_order( + &self, + signer: &SigningAccount, + tranche_key: &str, + ) -> RunnerExecuteResult { + self.dex.cancel_filled_limit_order( + MsgCancelLimitOrder { + creator: signer.address().to_string(), + tranche_key: tranche_key.to_string(), + }, + signer, + ) + } } fn find_attribute(events: &[Event], key: &str) -> Option { diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs index 527bf5ed8..842b0941a 100644 --- a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs @@ -18,8 +18,10 @@ use common::{ mod common; #[test] -fn init_on_duality() { +fn test_basic_ops() { let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("astro")]; + let orders_number = 1; + let app = NeutronTestApp::new(); let neutron = TestAppWrapper::bootstrap(&app).unwrap(); let owner = neutron.signer.address(); @@ -28,13 +30,13 @@ fn init_on_duality() { neutron, test_coins.clone(), ConcentratedPoolParams { - price_scale: Decimal::percent(50), + price_scale: Decimal::from_ratio(2u8, 1u8), ..common_pcl_params() }, OrderbookConfig { executor: Some(owner), liquidity_percent: Decimal::percent(5), - orders_number: 1, + orders_number, min_asset_0_order_size: Uint128::from(1_000u128), min_asset_1_order_size: Uint128::from(1_000u128), }, @@ -57,39 +59,12 @@ fn init_on_duality() { astroport.assets[&test_coins[0]].with_balance(1_000_000_000000u128), ]; + // Providing initial liquidity astroport .provide_liquidity(&user, &initial_balances) .unwrap(); - let balances = astroport - .pool_balances() - .unwrap() - .assets - .into_iter() - .sorted_by(|a, b| a.info.to_string().cmp(&b.info.to_string())) - .collect_vec(); - - assert_eq!(balances, initial_balances); - - // let orders = astroport - // .helper - // .list_orders(astroport.pair_addr.as_str()) - // .unwrap() - // .limit_orders - // .into_iter() - // .map(|order| order.tranche_key) - // .collect_vec(); - // dbg!(orders); - // let total_liquidity = astroport - // .helper - // .query_total_ob_liquidity(astroport.pair_addr.as_str()) - // .unwrap(); - // dbg!(total_liquidity); - - let swap_asset = astroport.assets[&test_coins[1]].with_balance(1_000_000000u128); - astroport.swap(&user, &swap_asset, None).unwrap(); - - dbg!(astroport.pool_balances().unwrap()); + assert_eq!(astroport.pool_balances().unwrap().assets, initial_balances); let orders = astroport .helper @@ -99,7 +74,22 @@ fn init_on_duality() { .into_iter() .map(|order| order.tranche_key) .collect_vec(); - dbg!(orders); + assert_eq!(orders.len(), (orders_number * 2) as usize); + let ob_liquidity = astroport + .helper + .query_total_ob_liquidity(astroport.pair_addr.as_str()) + .unwrap() + .into_iter() + .sorted_by(|a, b| a.denom.cmp(&b.denom)) + .collect_vec(); + assert_eq!( + ob_liquidity, + [coin(12388_891279, "astro"), coin(24_777_782558, "untrn")] + ); + + // Astroport swap ASTRO -> NTRN + let swap_asset = astroport.assets[&test_coins[1]].with_balance(1_000_000000u128); + astroport.swap(&user, &swap_asset, None).unwrap(); let dex_trader = astroport .helper @@ -110,47 +100,54 @@ fn init_on_duality() { ]) .unwrap(); - // let orders = astroport - // .helper - // .list_orders(astroport.pair_addr.as_str()) - // .unwrap(); - // dbg!(orders); - + // DEX swap NTRN -> ASTRO astroport .helper .swap_on_dex(&dex_trader, coin(1_000_000000u128, "untrn"), "astro", 0.49) .unwrap(); - // let bal = astroport - // .helper - // .bank - // .query_all_balances(&QueryAllBalancesRequest { - // address: dex_trader.address(), - // pagination: None, - // }) - // .unwrap(); - // dbg!(bal); + // One order was partially filled but still stays on the orderbook + let orders = astroport + .helper + .list_orders(astroport.pair_addr.as_str()) + .unwrap(); + assert_eq!(orders.limit_orders.len(), (orders_number * 2) as usize); - dbg!(astroport.pool_balances().unwrap()); + assert_eq!( + astroport.pool_balances().unwrap().assets, + [ + astroport.assets[&test_coins[1]].with_balance(500503_744799u128), + astroport.assets[&test_coins[0]].with_balance(999002_684480u128), + ] + ); + // Astroport swap NTRN -> ASTRO let swap_asset = astroport.assets[&test_coins[0]].with_balance(1_000_000000u128); astroport.swap_max_spread(&user, &swap_asset).unwrap(); + // DEX swap ASTRO -> NTRN astroport .helper .swap_on_dex(&dex_trader, coin(500_000000u128, "astro"), "untrn", 1.9) .unwrap(); + assert_eq!( + astroport.pool_balances().unwrap().assets, + [ + astroport.assets[&test_coins[1]].with_balance(500504_388512u128), + astroport.assets[&test_coins[0]].with_balance(999010_222418u128), + ] + ); + + // Astroport swap ASTRO -> NTRN let swap_asset = astroport.assets[&test_coins[1]].with_balance(500_000000u128); astroport.swap_max_spread(&user, &swap_asset).unwrap(); - dbg!(astroport.pool_balances().unwrap()); - let orders = astroport .helper .list_orders(astroport.pair_addr.as_str()) .unwrap(); - dbg!(orders); + assert_eq!(orders.limit_orders.len(), (orders_number * 2) as usize); // Creating a huge limit order which should be partially consumed by Astroport pair let whale = astroport @@ -158,10 +155,12 @@ fn init_on_duality() { .app .init_account(&[coin(2_000_000_000000u128, "untrn")]) .unwrap(); - astroport + let tranche_key = astroport .helper .limit_order(&whale, coin(1_000_000_000000u128, "untrn"), "astro", 0.3) - .unwrap(); + .unwrap() + .data + .tranche_key; astroport.sync_orders(&astroport.helper.signer).unwrap(); @@ -169,16 +168,30 @@ fn init_on_duality() { .helper .list_orders(astroport.pair_addr.as_str()) .unwrap(); - dbg!(orders); + assert_eq!(orders.limit_orders.len(), (orders_number * 2 - 1) as usize); + // Astroport swap NTRN -> ASTRO let swap_asset = astroport.assets[&test_coins[0]].with_balance(1000_000000u128); astroport.swap_max_spread(&user, &swap_asset).unwrap(); + // Still only 1 order because other side being constantly consumed by whale's order let orders = astroport .helper .list_orders(astroport.pair_addr.as_str()) .unwrap(); - dbg!(orders); + assert_eq!(orders.limit_orders.len(), (orders_number * 2 - 1) as usize); + + // Whale cancels order + astroport.helper.cancel_order(&whale, &tranche_key).unwrap(); + + // Swap to trigger new orders placement + let swap_asset = astroport.assets[&test_coins[0]].with_balance(1_000000u128); + astroport.swap_max_spread(&user, &swap_asset).unwrap(); - panic!("Test panic") + // Confirm we have all orders back + let orders = astroport + .helper + .list_orders(astroport.pair_addr.as_str()) + .unwrap(); + assert_eq!(orders.limit_orders.len(), (orders_number * 2) as usize); } diff --git a/packages/astroport_pcl_common/src/utils.rs b/packages/astroport_pcl_common/src/utils.rs index 76273dc54..2d4e4f4c3 100644 --- a/packages/astroport_pcl_common/src/utils.rs +++ b/packages/astroport_pcl_common/src/utils.rs @@ -185,6 +185,7 @@ pub fn before_swap_check(pools: &[DecimalAsset], offer_amount: Decimal256) -> St } /// This structure is for internal use only. Represents swap's result. +#[derive(Debug)] pub struct SwapResult { pub dy: Decimal256, pub spread_fee: Decimal256, @@ -286,9 +287,9 @@ pub fn compute_swap( // Derive spread using oracle price let spread_fee = if ask_ind == 1 { dy /= config.pool_state.price_state.price_scale; - (offer_amount * config.pool_state.price_state.oracle_price).saturating_sub(dy) - } else { (offer_amount / config.pool_state.price_state.oracle_price).saturating_sub(dy) + } else { + (offer_amount * config.pool_state.price_state.oracle_price).saturating_sub(dy) }; let fee_rate = config.pool_params.fee(&ixs); From 0d3cb71161a25e2797debe5a565fd205f471adc3 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Tue, 4 Feb 2025 12:33:53 +0300 Subject: [PATCH 09/24] fix last balance accounting --- .../pair_concentrated_duality/src/execute.rs | 49 ++++++++++++++----- .../src/orderbook/execute.rs | 11 ++++- .../src/orderbook/state.rs | 12 +++-- .../src/orderbook/utils.rs | 38 +++++++++++--- .../pair_concentrated_duality/src/reply.rs | 1 - .../tests/common/astroport_wrapper.rs | 13 ++--- .../tests/common/helper.rs | 2 + .../tests/common/neutron_wrapper.rs | 27 +++++++++- .../tests/pcl_duality_e2e.rs | 37 +++++++++++++- .../src/pair_concentrated_duality.rs | 4 ++ 10 files changed, 156 insertions(+), 38 deletions(-) diff --git a/contracts/pair_concentrated_duality/src/execute.rs b/contracts/pair_concentrated_duality/src/execute.rs index cfe1ccf02..de7ec01ed 100644 --- a/contracts/pair_concentrated_duality/src/execute.rs +++ b/contracts/pair_concentrated_duality/src/execute.rs @@ -266,8 +266,16 @@ pub fn provide_liquidity( CONFIG.save(deps.storage, &config)?; + let pools_u128 = pools + .iter() + .map(|asset| { + let prec = precisions.get_precision(&asset.info).unwrap(); + let amount = asset.amount.to_uint(prec)?; + Ok(asset.info.with_balance(amount)) + }) + .collect::>>()?; let submsgs = ob_state.flatten_msgs_and_add_callback( - &liquidity, + &pools_u128, &[cancel_msgs, mint_lp_messages], order_msgs, ); @@ -305,7 +313,7 @@ fn withdraw_liquidity( let precisions = Precisions::new(deps.storage)?; - let mut liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; + let liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; // This call fetches possible cumulative trade let maybe_cumulative_trade = @@ -361,6 +369,15 @@ fn withdraw_liquidity( get_xcp(d, config.pool_state.price_state.price_scale) / (total_share - amount).to_decimal256(LP_TOKEN_PRECISION)?; + let mut pools_u128 = pools + .iter() + .map(|asset| { + let prec = precisions.get_precision(&asset.info).unwrap(); + let amount = asset.amount.to_uint(prec)?; + Ok(asset.info.with_balance(amount)) + }) + .collect::>>()?; + let refund_assets = refund_assets .into_iter() .enumerate() @@ -368,12 +385,9 @@ fn withdraw_liquidity( let prec = precisions.get_precision(&asset.info).unwrap(); let amount = asset.amount.to_uint(prec)?; - liquidity.contract[ind].amount -= amount; + pools_u128[ind].amount -= amount; - Ok(Asset { - info: asset.info, - amount, - }) + Ok(asset.info.with_balance(amount)) }) .collect::>>()?; @@ -393,7 +407,7 @@ fn withdraw_liquidity( let order_msgs = ob_state.deploy_orders(&env, &config, &xs, &precisions, deps.api)?; let submsgs = ob_state.flatten_msgs_and_add_callback( - &liquidity, + &pools_u128, &[cancel_msgs, withdraw_messages], order_msgs, ); @@ -435,7 +449,7 @@ fn swap( let mut ob_state = OrderbookState::load(deps.storage)?; - let mut liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; + let liquidity = Liquidity::new(deps.querier, &config, &ob_state, false)?; // This call fetches possible cumulative trade let maybe_cumulative_trade = @@ -545,7 +559,16 @@ fn swap( .with_balance(return_amount) .into_msg(&receiver)?]; - liquidity.contract[ask_ind].amount -= return_amount; + let mut pools_u128 = pools + .iter() + .map(|asset| { + let prec = precisions.get_precision(&asset.info).unwrap(); + let amount = asset.amount.to_uint(prec)?; + Ok(asset.info.with_balance(amount)) + }) + .collect::>>()?; + pools_u128[offer_ind].amount += offer_asset.amount; + pools_u128[ask_ind].amount -= return_amount; // Send the shared fee let mut fee_share_amount = Uint128::zero(); @@ -554,7 +577,7 @@ fn swap( if !fee_share_amount.is_zero() { let fee = pools[ask_ind].info.with_balance(fee_share_amount); messages.push(fee.into_msg(&fee_share.recipient)?); - liquidity.contract[ask_ind].amount -= fee_share_amount; + pools_u128[ask_ind].amount -= fee_share_amount; } } @@ -565,7 +588,7 @@ fn swap( if !maker_fee.is_zero() { let fee = pools[ask_ind].info.with_balance(maker_fee); messages.push(fee.into_msg(fee_address)?); - liquidity.contract[ask_ind].amount -= maker_fee; + pools_u128[ask_ind].amount -= maker_fee; } } @@ -578,7 +601,7 @@ fn swap( CONFIG.save(deps.storage, &config)?; let submsgs = - ob_state.flatten_msgs_and_add_callback(&liquidity, &[cancel_msgs, messages], order_msgs); + ob_state.flatten_msgs_and_add_callback(&pools_u128, &[cancel_msgs, messages], order_msgs); ob_state.save(deps.storage)?; diff --git a/contracts/pair_concentrated_duality/src/orderbook/execute.rs b/contracts/pair_concentrated_duality/src/orderbook/execute.rs index 9e3dab5b0..3b24c1847 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/execute.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/execute.rs @@ -1,6 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ attr, ensure_eq, to_json_string, Decimal256, Deps, DepsMut, Env, MessageInfo, Response, + StdResult, }; use itertools::Itertools; @@ -173,8 +174,16 @@ pub fn sync_pool_with_orderbook( let balances = pools.iter().map(|asset| asset.amount).collect_vec(); let order_msgs = ob_state.deploy_orders(&env, &config, &balances, &precisions, deps.api)?; + let pools_u128 = pools + .iter() + .map(|asset| { + let prec = precisions.get_precision(&asset.info).unwrap(); + let amount = asset.amount.to_uint(prec)?; + Ok(asset.info.with_balance(amount)) + }) + .collect::>>()?; let submsgs = - ob_state.flatten_msgs_and_add_callback(&liquidity, &[cancel_msgs], order_msgs); + ob_state.flatten_msgs_and_add_callback(&pools_u128, &[cancel_msgs], order_msgs); Ok(response .add_attributes([ diff --git a/contracts/pair_concentrated_duality/src/orderbook/state.rs b/contracts/pair_concentrated_duality/src/orderbook/state.rs index f4f0d97d3..e413970e7 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/state.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/state.rs @@ -22,7 +22,7 @@ use astroport_pcl_common::state::{Config, Precisions}; use crate::error::ContractError; use crate::orderbook::consts::{MAX_LIQUIDITY_PERCENT, MIN_LIQUIDITY_PERCENT, ORDER_SIZE_LIMITS}; use crate::orderbook::custom_types::CustomQueryAllLimitOrderTrancheUserByAddressResponse; -use crate::orderbook::utils::{compute_swap, Liquidity, SpotOrdersFactory}; +use crate::orderbook::utils::{compute_swap, SpotOrdersFactory}; macro_rules! validate_param { ($name:ident, $val:expr, $min:expr, $max:expr) => { @@ -59,6 +59,10 @@ pub struct OrderbookState { pub enabled: bool, /// Snapshot of total balances before entering reply pub pre_reply_balances: Vec, + /// Due to possible rounding issues on Duality side we have to set price tolerance, + /// which serves as a worsening factor for the end price from PCL. + /// Should be relatively low something like 1-10 bps. + pub avg_price_adjustment: Decimal, } const OB_CONFIG: Item = Item::new("orderbook_config"); @@ -75,6 +79,7 @@ impl OrderbookState { enabled: false, executor: orderbook_config.executor.map(Addr::unchecked), pre_reply_balances: vec![], + avg_price_adjustment: orderbook_config.avg_price_adjustment, }; config.validate(api)?; @@ -359,6 +364,7 @@ impl OrderbookState { &config.pair_info.asset_infos, asset_0_precision, asset_1_precision, + self.avg_price_adjustment.into(), ); // Equal heights algorithm @@ -407,7 +413,7 @@ impl OrderbookState { /// if orderbook integration is enabled. pub fn flatten_msgs_and_add_callback( &mut self, - liquidity: &Liquidity, + total_liquidity: &[Asset], messages: &[Vec], order_msgs: Vec, ) -> Vec { @@ -424,7 +430,7 @@ impl OrderbookState { last.reply_on = ReplyOn::Success; } - self.pre_reply_balances = liquidity.total(); + self.pre_reply_balances = total_liquidity.to_vec(); submsgs } diff --git a/contracts/pair_concentrated_duality/src/orderbook/utils.rs b/contracts/pair_concentrated_duality/src/orderbook/utils.rs index c5346e0ab..61c993a98 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/utils.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/utils.rs @@ -66,10 +66,16 @@ pub struct SpotOrdersFactory { multiplier: [Decimal256; 2], precision: [u8; 2], denoms: Vec, + avg_price_adjustment: Decimal256, } impl SpotOrdersFactory { - pub fn new(asset_infos: &[AssetInfo], asset_0_precision: u8, asset_1_precision: u8) -> Self { + pub fn new( + asset_infos: &[AssetInfo], + asset_0_precision: u8, + asset_1_precision: u8, + avg_price_adjustment: Decimal256, + ) -> Self { let denoms = asset_infos .iter() .map(|info| match &info { @@ -86,6 +92,7 @@ impl SpotOrdersFactory { ], precision: [asset_0_precision, asset_1_precision], denoms, + avg_price_adjustment, } } @@ -123,8 +130,17 @@ impl SpotOrdersFactory { self.orders .into_iter() .map(|order| { + // Worsen the price to make sure rounding errors are covered in favor of our pool + let limit_price = order.price + self.avg_price_adjustment * order.price; + if order.is_buy { let limit_sell_price = price_to_duality_notation( + limit_price, + self.precision[1], + self.precision[0], + ) + .unwrap(); + let min_average_sell_price = price_to_duality_notation( order.price, self.precision[1], self.precision[0], @@ -132,7 +148,7 @@ impl SpotOrdersFactory { .unwrap(); api.debug(&format!( - "buy: limit_sell_price: {limit_sell_price}, amount_in: {}", + "buy: limit_sell_price: {limit_sell_price} min_average_sell_price: {min_average_sell_price}, amount_in: {}", (order.amount * self.multiplier[1]) .to_uint_floor() .to_string() @@ -151,21 +167,27 @@ impl SpotOrdersFactory { receiver: sender.to_string(), token_in: self.denoms[1].clone(), token_out: self.denoms[0].clone(), - limit_sell_price: Some(limit_sell_price), + limit_sell_price: Some(limit_sell_price.clone()), tick_index_in_to_out: 0, - min_average_sell_price: None, + min_average_sell_price: Some(min_average_sell_price), } .into() } else { let limit_sell_price = price_to_duality_notation( - order.price, + limit_price, self.precision[0], self.precision[1], ) .unwrap(); + let min_average_sell_price = price_to_duality_notation( + order.price, + self.precision[1], + self.precision[0], + ) + .unwrap(); api.debug(&format!( - "sell: limit_sell_price: {limit_sell_price}, amount_in: {}", + "sell: limit_sell_price: {limit_sell_price} min_average_sell_price: {min_average_sell_price}, amount_in: {}", (order.amount * self.multiplier[0]) .to_uint_floor() .to_string() @@ -184,9 +206,9 @@ impl SpotOrdersFactory { receiver: sender.to_string(), token_in: self.denoms[0].clone(), token_out: self.denoms[1].clone(), - limit_sell_price: Some(limit_sell_price), + limit_sell_price: Some(limit_sell_price.clone()), tick_index_in_to_out: 0, - min_average_sell_price: None, + min_average_sell_price: Some(min_average_sell_price), } .into() } diff --git a/contracts/pair_concentrated_duality/src/reply.rs b/contracts/pair_concentrated_duality/src/reply.rs index fbc6f29a3..228a410dc 100644 --- a/contracts/pair_concentrated_duality/src/reply.rs +++ b/contracts/pair_concentrated_duality/src/reply.rs @@ -40,7 +40,6 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { - // Query total liquidity sitting on orderbook and cache it in the contract state let mut ob_state = OrderbookState::load(deps.storage)?; ob_state.fetch_all_orders(deps.as_ref(), &env.contract.address)?; diff --git a/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs b/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs index 5d3b5785e..585ab37b3 100644 --- a/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs +++ b/contracts/pair_concentrated_duality/tests/common/astroport_wrapper.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use anyhow::Result as AnyResult; -use cosmwasm_std::{coin, coins, to_json_binary, Addr, Coin, Decimal}; +use cosmwasm_std::{coin, to_json_binary, Addr, Coin, Decimal}; use itertools::Itertools; use neutron_test_tube::cosmrs::proto::cosmwasm::wasm::v1::MsgExecuteContractResponse; use neutron_test_tube::{Account, ExecuteResponse, SigningAccount}; @@ -24,7 +24,7 @@ use astroport_test::convert::f64_to_dec; use super::neutron_wrapper::TestAppWrapper; -type ExecuteMsg = astroport::pair::ExecuteMsgExt; +type ExecuteMsg = pair::ExecuteMsgExt; const INIT_BALANCE: u128 = u128::MAX; @@ -231,19 +231,14 @@ impl<'a> AstroportHelper<'a> { .map(|_| ()) } - pub fn withdraw_liquidity(&mut self, sender: &SigningAccount, amount: u128) -> AnyResult<()> { + pub fn withdraw_liquidity(&self, sender: &SigningAccount, lp_tokens: Coin) -> AnyResult<()> { let msg = ExecuteMsg::WithdrawLiquidity { assets: vec![], min_assets_to_receive: None, }; self.helper - .execute_contract( - sender, - self.pair_addr.as_str(), - &msg, - &coins(amount, &self.lp_token), - ) + .execute_contract(sender, self.pair_addr.as_str(), &msg, &vec![lp_tokens]) .map(|_| ()) } diff --git a/contracts/pair_concentrated_duality/tests/common/helper.rs b/contracts/pair_concentrated_duality/tests/common/helper.rs index fe9c8d37b..8374574bd 100644 --- a/contracts/pair_concentrated_duality/tests/common/helper.rs +++ b/contracts/pair_concentrated_duality/tests/common/helper.rs @@ -2,6 +2,7 @@ #![allow(dead_code)] use std::collections::HashMap; +use std::str::FromStr; use anyhow::Result as AnyResult; use cosmwasm_schema::cw_serde; @@ -223,6 +224,7 @@ impl Helper { min_asset_0_order_size: Uint128::from(1000u128), min_asset_1_order_size: Uint128::from(1000u128), executor: Some(owner.to_string()), + avg_price_adjustment: Decimal::from_str("0.001").unwrap(), }, }) .unwrap(), diff --git a/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs b/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs index 0372f3624..744a1fd62 100644 --- a/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs +++ b/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs @@ -17,6 +17,7 @@ use neutron_std::types::neutron::dex::{ MsgPlaceLimitOrderResponse, QueryAllLimitOrderTrancheUserByAddressRequest, QueryAllLimitOrderTrancheUserByAddressResponse, QuerySimulateCancelLimitOrderRequest, }; +use neutron_test_tube::cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest; use neutron_test_tube::{ Account, Bank, Dex, ExecuteResponse, Module, NeutronTestApp, RunnerExecuteResult, SigningAccount, Wasm, @@ -254,6 +255,20 @@ impl<'a> TestAppWrapper<'a> { .collect() } + pub fn query_balance(&self, address: &str, denom: &str) -> AnyResult { + self.bank + .query_balance(&QueryBalanceRequest { + address: address.to_string(), + denom: denom.to_string(), + }) + .map_err(Into::into) + .and_then(|resp| { + resp.balance + .map(|proto_coin| coin(proto_coin.amount.parse().unwrap(), proto_coin.denom)) + .ok_or_else(|| anyhow!("No balance found for {denom} in {address}")) + }) + } + pub fn swap_on_dex( &self, signer: &SigningAccount, @@ -284,6 +299,16 @@ impl<'a> TestAppWrapper<'a> { coin_in: Coin, to_denom: &str, price: f64, + ) -> RunnerExecuteResult { + self.limit_order_precise(signer, coin_in, to_denom, price * 1e27) + } + + pub fn limit_order_precise( + &self, + signer: &SigningAccount, + coin_in: Coin, + to_denom: &str, + price: f64, ) -> RunnerExecuteResult { #[allow(deprecated)] let msg = MsgPlaceLimitOrder { @@ -296,7 +321,7 @@ impl<'a> TestAppWrapper<'a> { order_type: 0, expiration_time: None, max_amount_out: None, - limit_sell_price: Some((price * 1e27).to_string()), + limit_sell_price: Some(price.to_string()), min_average_sell_price: None, }; self.dex.place_limit_order(msg, signer) diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs index 842b0941a..631953745 100644 --- a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs @@ -2,9 +2,10 @@ #![cfg(feature = "test-tube")] #![allow(dead_code)] +use std::str::FromStr; + use cosmwasm_std::{coin, Decimal, Uint128}; use itertools::Itertools; -use neutron_test_tube::cosmrs::proto::cosmos::bank::v1beta1::QueryAllBalancesRequest; use neutron_test_tube::{Account, NeutronTestApp}; use astroport::asset::AssetInfoExt; @@ -39,6 +40,7 @@ fn test_basic_ops() { orders_number, min_asset_0_order_size: Uint128::from(1_000u128), min_asset_1_order_size: Uint128::from(1_000u128), + avg_price_adjustment: Decimal::from_str("0.0001").unwrap(), }, ) .unwrap(); @@ -135,7 +137,7 @@ fn test_basic_ops() { astroport.pool_balances().unwrap().assets, [ astroport.assets[&test_coins[1]].with_balance(500504_388512u128), - astroport.assets[&test_coins[0]].with_balance(999010_222418u128), + astroport.assets[&test_coins[0]].with_balance(999010_420620u128), ] ); @@ -184,6 +186,23 @@ fn test_basic_ops() { // Whale cancels order astroport.helper.cancel_order(&whale, &tranche_key).unwrap(); + // let res = astroport + // .helper + // .dex + // .tick_liquidity_all(&QueryAllTickLiquidityRequest { + // pair_id: "untrn<>astro".to_string(), + // token_in: "untrn".to_string(), + // pagination: None, + // }) + // .unwrap(); + // dbg!(res); + // dbg!(astroport.helper.list_orders(&whale.address()).unwrap()); + // dbg!(astroport + // .helper + // .list_orders(astroport.pair_addr.as_str()) + // .unwrap()); + // dbg!(astroport.helper.list_orders(&dex_trader.address()).unwrap()); + // Swap to trigger new orders placement let swap_asset = astroport.assets[&test_coins[0]].with_balance(1_000000u128); astroport.swap_max_spread(&user, &swap_asset).unwrap(); @@ -194,4 +213,18 @@ fn test_basic_ops() { .list_orders(astroport.pair_addr.as_str()) .unwrap(); assert_eq!(orders.limit_orders.len(), (orders_number * 2) as usize); + + // Ensure that the main LP can withdraw all liquidity + let lp_token = astroport + .helper + .query_balance(&user.address(), &astroport.lp_token) + .unwrap(); + astroport.withdraw_liquidity(&user, lp_token).unwrap(); + + // Confirm we no longer have any orders + let orders = astroport + .helper + .list_orders(astroport.pair_addr.as_str()) + .unwrap(); + assert_eq!(orders.limit_orders.len(), 0); } diff --git a/packages/astroport/src/pair_concentrated_duality.rs b/packages/astroport/src/pair_concentrated_duality.rs index 8cc610702..79922ea90 100644 --- a/packages/astroport/src/pair_concentrated_duality.rs +++ b/packages/astroport/src/pair_concentrated_duality.rs @@ -15,6 +15,10 @@ pub struct OrderbookConfig { pub min_asset_1_order_size: Uint128, /// Percent of liquidity to be deployed to the orderbook pub liquidity_percent: Decimal, + /// Due to possible rounding issues on Duality side we have to set price tolerance, + /// which serves as a worsening factor for the end price from PCL. + /// Should be relatively low something like 1-10 bps. + pub avg_price_adjustment: Decimal, } #[cw_serde] From 650afca4c248b63dc8f5eecc8c91f5c40f7d4e9e Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Wed, 5 Feb 2025 12:53:24 +0300 Subject: [PATCH 10/24] fix price conversion --- .../src/orderbook/utils.rs | 65 +++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/contracts/pair_concentrated_duality/src/orderbook/utils.rs b/contracts/pair_concentrated_duality/src/orderbook/utils.rs index 61c993a98..467bffd77 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/utils.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/utils.rs @@ -226,7 +226,7 @@ fn price_to_duality_notation( ) -> Result { let prec_diff = quote_precision as i8 - base_precision as i8; let price = match prec_diff.cmp(&0) { - Ordering::Less => price / Decimal256::from_integer(10u128.pow(prec_diff as u32)), + Ordering::Less => price / Decimal256::from_integer(10u128.pow(prec_diff.abs() as u32)), Ordering::Equal => price, Ordering::Greater => price * Decimal256::from_integer(10u128.pow(prec_diff as u32)), } @@ -340,35 +340,34 @@ pub fn fetch_cumulative_trade( } } -// TODO: fix tests -// #[cfg(test)] -// mod unit_tests { -// use super::*; -// -// #[test] -// fn test_sci_notation_conversion() { -// let price = Decimal256::from_ratio(1u8, 3000u64); -// let base_precision = 6; -// let quote_precision = 18; -// assert_eq!( -// price_to_sci_notation(price, base_precision, quote_precision), -// "333333333.333333" -// ); -// -// let price = Decimal256::from_ratio(3000u64, 1u8); -// let base_precision = 18; -// let quote_precision = 6; -// assert_eq!( -// price_to_sci_notation(price, base_precision, quote_precision), -// "3000E-12" -// ); -// -// let price = Decimal256::from_ratio(1u8, 2u8); -// let base_precision = 6; -// let quote_precision = 6; -// assert_eq!( -// price_to_sci_notation(price, base_precision, quote_precision), -// "0.5" -// ); -// } -// } +#[cfg(test)] +mod unit_tests { + use super::*; + + #[test] + fn test_sci_notation_conversion() { + let price = Decimal256::from_ratio(1u8, 3000u64); + let base_precision = 6; + let quote_precision = 18; + assert_eq!( + price_to_duality_notation(price, base_precision, quote_precision).unwrap(), + "333333333333333000000000000000000000" + ); + + let price = Decimal256::from_ratio(3000u64, 1u8); + let base_precision = 18; + let quote_precision = 6; + assert_eq!( + price_to_duality_notation(price, base_precision, quote_precision).unwrap(), + "3000000000000000000" + ); + + let price = Decimal256::from_ratio(1u8, 2u8); + let base_precision = 6; + let quote_precision = 6; + assert_eq!( + price_to_duality_notation(price, base_precision, quote_precision).unwrap(), + "500000000000000000000000000" + ); + } +} From ecc5e162f517099d3e690e17ae87404d99a4ad6d Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:00:42 +0300 Subject: [PATCH 11/24] test integration with different decimals --- .../pair_concentrated_duality/src/execute.rs | 8 ++ .../tests/common/neutron_wrapper.rs | 16 ++- .../tests/pcl_duality_e2e.rs | 125 ++++++++++++++++++ 3 files changed, 146 insertions(+), 3 deletions(-) diff --git a/contracts/pair_concentrated_duality/src/execute.rs b/contracts/pair_concentrated_duality/src/execute.rs index de7ec01ed..9c882c56c 100644 --- a/contracts/pair_concentrated_duality/src/execute.rs +++ b/contracts/pair_concentrated_duality/src/execute.rs @@ -170,6 +170,10 @@ pub fn provide_liquidity( let maybe_cumulative_trade = fetch_cumulative_trade(&precisions, &ob_state.last_balances, &liquidity.orderbook)?; + // TODO: delete me + deps.api + .debug(&format!("provide: {:?}", maybe_cumulative_trade)); + let mut pools = liquidity.total_dec(&precisions)?; let old_real_price = config.pool_state.price_state.last_price; @@ -319,6 +323,10 @@ fn withdraw_liquidity( let maybe_cumulative_trade = fetch_cumulative_trade(&precisions, &ob_state.last_balances, &liquidity.orderbook)?; + // TODO: delete me + deps.api + .debug(&format!("withdraw: {:?}", maybe_cumulative_trade)); + let mut pools = liquidity.total_dec(&precisions)?; let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)?; diff --git a/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs b/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs index 744a1fd62..c0567d407 100644 --- a/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs +++ b/contracts/pair_concentrated_duality/tests/common/neutron_wrapper.rs @@ -269,12 +269,12 @@ impl<'a> TestAppWrapper<'a> { }) } - pub fn swap_on_dex( + pub fn swap_on_dex_precise( &self, signer: &SigningAccount, coin_in: Coin, to_denom: &str, - price: f64, + price: u128, ) -> RunnerExecuteResult { #[allow(deprecated)] let msg = MsgPlaceLimitOrder { @@ -287,12 +287,22 @@ impl<'a> TestAppWrapper<'a> { order_type: 1, expiration_time: None, max_amount_out: None, - limit_sell_price: Some((price * 1e27).to_string()), + limit_sell_price: Some(price.to_string()), min_average_sell_price: None, }; self.dex.place_limit_order(msg, signer) } + pub fn swap_on_dex( + &self, + signer: &SigningAccount, + coin_in: Coin, + to_denom: &str, + price: f64, + ) -> RunnerExecuteResult { + self.swap_on_dex_precise(signer, coin_in, to_denom, (price * 1e27) as u128) + } + pub fn limit_order( &self, signer: &SigningAccount, diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs index 631953745..b88a96430 100644 --- a/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_e2e.rs @@ -228,3 +228,128 @@ fn test_basic_ops() { .unwrap(); assert_eq!(orders.limit_orders.len(), 0); } + +#[test] +fn test_different_decimals() { + let test_coins = vec![ + TestCoin::native_precise("aeth", 18), + TestCoin::native("astro"), + ]; + let orders_number = 1; + + let app = NeutronTestApp::new(); + let neutron = TestAppWrapper::bootstrap(&app).unwrap(); + let owner = neutron.signer.address(); + + let astroport = AstroportHelper::new( + neutron, + test_coins.clone(), + ConcentratedPoolParams { + price_scale: Decimal::from_ratio(2u8, 1u8), + ..common_pcl_params() + }, + OrderbookConfig { + executor: Some(owner), + liquidity_percent: Decimal::percent(5), + orders_number, + min_asset_0_order_size: Uint128::from(1_000u128), + min_asset_1_order_size: Uint128::from(1_000u128), + avg_price_adjustment: Decimal::from_str("0.0001").unwrap(), + }, + ) + .unwrap(); + + astroport.enable_orderbook(&astroport.owner, true).unwrap(); + + let user = astroport + .helper + .app + .init_account(&[ + coin(1000000_000000u128, "untrn"), // for gas fees + coin(1_100_000u128 * 1e18 as u128, "aeth"), + coin(505_000_000000u128, "astro"), + ]) + .unwrap(); + + let initial_balances = [ + astroport.assets[&test_coins[0]].with_balance(1_000_000u128 * 1e18 as u128), + astroport.assets[&test_coins[1]].with_balance(500_000_000000u128), + ]; + + // Providing initial liquidity + astroport + .provide_liquidity(&user, &initial_balances) + .unwrap(); + + assert_eq!(astroport.pool_balances().unwrap().assets, initial_balances); + + // Astroport swap ASTRO -> ETH + let swap_asset = astroport.assets[&test_coins[1]].with_balance(1_000_000000u128); + astroport.swap(&user, &swap_asset, None).unwrap(); + + let dex_trader = astroport + .helper + .app + .init_account(&[ + coin(1000000_000000u128, "untrn"), // for gas fees + coin(10_000u128 * 1e18 as u128, "aeth"), + ]) + .unwrap(); + + // DEX swap ETH -> ASTRO + astroport + .helper + .swap_on_dex_precise( + &dex_trader, + coin(1_000u128 * 1e18 as u128, "aeth"), + "astro", + 490000000000000, // 0.49e-12 uastro per aeth + ) + .unwrap(); + + let astro_bal = astroport + .helper + .query_balance(&dex_trader.address(), "astro") + .unwrap(); + assert_eq!(astro_bal.amount.u128(), 496_206891); + + let dex_trader2 = astroport + .helper + .app + .init_account(&[ + coin(1000000_000000u128, "untrn"), // for gas fees + coin(10_000_000000u128, "astro"), + ]) + .unwrap(); + + // DEX swap ASTRO -> ETH + astroport + .helper + .swap_on_dex( + &dex_trader2, + coin(1_000_000000u128, "astro"), + "aeth", + 2018138624033.183, // 2018138624033.183 aeth per astro + ) + .unwrap(); + + let eth_bal = astroport + .helper + .query_balance(&dex_trader2.address(), "aeth") + .unwrap(); + assert_eq!(eth_bal.amount.u128(), 1978_350157256753797143); + + // Ensure that the main LP can withdraw all liquidity + let lp_token = astroport + .helper + .query_balance(&user.address(), &astroport.lp_token) + .unwrap(); + astroport.withdraw_liquidity(&user, lp_token).unwrap(); + + // Confirm we no longer have any orders + let orders = astroport + .helper + .list_orders(astroport.pair_addr.as_str()) + .unwrap(); + assert_eq!(orders.limit_orders.len(), 0); +} From 29314f645e87d4567c1b1505c61d050b1e11fb02 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:48:44 +0300 Subject: [PATCH 12/24] add unit test --- .../src/orderbook/execute.rs | 2 + .../src/orderbook/utils.rs | 139 ++++++++++++++++-- 2 files changed, 131 insertions(+), 10 deletions(-) diff --git a/contracts/pair_concentrated_duality/src/orderbook/execute.rs b/contracts/pair_concentrated_duality/src/orderbook/execute.rs index 3b24c1847..88a0e5957 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/execute.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/execute.rs @@ -25,7 +25,9 @@ use super::state::OrderbookState; /// In this context, Astroport always charges protocol fees from quote asset. #[cw_serde] pub struct CumulativeTrade { + /// An asset that was sold pub base_asset: DecimalAsset, + /// An asset that was bought pub quote_asset: DecimalAsset, } diff --git a/contracts/pair_concentrated_duality/src/orderbook/utils.rs b/contracts/pair_concentrated_duality/src/orderbook/utils.rs index 467bffd77..acb53c6ae 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/utils.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/utils.rs @@ -1,7 +1,8 @@ use std::cmp::Ordering; use cosmwasm_std::{ - Addr, Api, CosmosMsg, Decimal256, OverflowError, QuerierWrapper, StdResult, Uint128, Uint256, + ensure, ensure_eq, Addr, Api, CosmosMsg, Decimal256, OverflowError, QuerierWrapper, StdError, + StdResult, Uint128, Uint256, }; use itertools::Itertools; use neutron_std::types::neutron::dex::MsgPlaceLimitOrder; @@ -321,17 +322,42 @@ pub fn fetch_cumulative_trade( let maybe_trade = match last_balances[0].amount.cmp(&new_balances[0].amount) { // We sold asset 0 for asset 1 - Ordering::Less => Some(CumulativeTrade { - base_asset: diff_to_dec_asset(1)?, - quote_asset: diff_to_dec_asset(0)?, - }), + Ordering::Less => { + ensure!( + last_balances[1].amount > new_balances[1].amount, + StdError::generic_err( + "Invalid balance difference while calculating cumulative trade" + ) + ); + Some(CumulativeTrade { + base_asset: diff_to_dec_asset(1)?, + quote_asset: diff_to_dec_asset(0)?, + }) + } // We bought asset 0 with asset 1 - Ordering::Greater => Some(CumulativeTrade { - base_asset: diff_to_dec_asset(0)?, - quote_asset: diff_to_dec_asset(1)?, - }), + Ordering::Greater => { + ensure!( + last_balances[1].amount < new_balances[1].amount, + StdError::generic_err( + "Invalid balance difference while calculating cumulative trade" + ) + ); + Some(CumulativeTrade { + base_asset: diff_to_dec_asset(0)?, + quote_asset: diff_to_dec_asset(1)?, + }) + } // No trade happened - Ordering::Equal => None, + Ordering::Equal => { + ensure_eq!( + last_balances[1].amount, + new_balances[1].amount, + StdError::generic_err( + "Invalid balance difference while calculating cumulative trade" + ) + ); + None + } }; Ok(maybe_trade) @@ -342,6 +368,10 @@ pub fn fetch_cumulative_trade( #[cfg(test)] mod unit_tests { + use cosmwasm_std::testing::MockStorage; + + use astroport_test::convert::f64_to_dec; + use super::*; #[test] @@ -370,4 +400,93 @@ mod unit_tests { "500000000000000000000000000" ); } + + #[test] + fn test_cumulative_trade() { + let mut storage = MockStorage::new(); + for (asset_info, precision) in [ + (AssetInfo::native("untrn"), 6), + (AssetInfo::native("astro"), 8), + ] { + Precisions::PRECISIONS + .save(&mut storage, asset_info.to_string(), &precision) + .unwrap(); + } + + let precisions = Precisions::new(&storage).unwrap(); + let last_balances = vec![ + Asset::native("astro", 1000_00000000u128), + Asset::native("untrn", 1000_000000u128), + ]; + let new_balances = vec![ + Asset::native("untrn", 950_000000u128), + Asset::native("astro", 1050_00000000u128), + ]; + + let trade = fetch_cumulative_trade(&precisions, &last_balances, &new_balances) + .unwrap() + .unwrap(); + assert_eq!( + trade, + CumulativeTrade { + base_asset: AssetInfo::native("untrn").with_dec_balance(f64_to_dec(50.0)), + quote_asset: AssetInfo::native("astro").with_dec_balance(f64_to_dec(50.0)), + } + ); + + // Trade in opposite direction + let new_balances = vec![ + Asset::native("untrn", 1050_000000u128), + Asset::native("astro", 950_00000000u128), + ]; + let trade = fetch_cumulative_trade(&precisions, &last_balances, &new_balances) + .unwrap() + .unwrap(); + assert_eq!( + trade, + CumulativeTrade { + base_asset: AssetInfo::native("astro").with_dec_balance(f64_to_dec(50.0)), + quote_asset: AssetInfo::native("untrn").with_dec_balance(f64_to_dec(50.0)), + } + ); + + // No trade + assert_eq!( + fetch_cumulative_trade(&precisions, &last_balances, &last_balances).unwrap(), + None + ); + + // Invalid balance for 2nd asset while 1st asset is the same + let new_balances = vec![ + Asset::native("untrn", 1000_000000u128), + Asset::native("astro", 950_00000000u128), + ]; + let trade = fetch_cumulative_trade(&precisions, &last_balances, &new_balances).unwrap_err(); + assert_eq!( + trade.to_string(), + "Generic error: Invalid balance difference while calculating cumulative trade" + ); + + // Invalid balance for 2nd asset while 1st asset increased + let new_balances = vec![ + Asset::native("untrn", 1050_000000u128), + Asset::native("astro", 1000_00000000u128), + ]; + let trade = fetch_cumulative_trade(&precisions, &last_balances, &new_balances).unwrap_err(); + assert_eq!( + trade.to_string(), + "Generic error: Invalid balance difference while calculating cumulative trade" + ); + + // Invalid balance for 1st asset while 2nd asset increased + let new_balances = vec![ + Asset::native("untrn", 1001_000000u128), + Asset::native("astro", 1050_00000000u128), + ]; + let trade = fetch_cumulative_trade(&precisions, &last_balances, &new_balances).unwrap_err(); + assert_eq!( + trade.to_string(), + "Generic error: Invalid balance difference while calculating cumulative trade" + ); + } } From 61397ace1c4a5c0948857087891bba58c03ff85f Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:52:23 +0300 Subject: [PATCH 13/24] add seamless migration to orderbook --- Cargo.lock | 6 +- .../pair_concentrated_duality/Cargo.toml | 14 +- .../pair_concentrated_duality/src/execute.rs | 3 +- .../src/instantiate.rs | 13 +- .../pair_concentrated_duality/src/migrate.rs | 131 ++++--- .../src/orderbook/mod.rs | 2 +- .../tests/common/helper.rs | 115 ++++-- .../tests/pcl_duality_integration.rs | 335 +++++++++++++++++- .../src/pair_concentrated_duality.rs | 8 + .../src/modules/neutron_stargate.rs | 37 +- 10 files changed, 540 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0a292a67..b3fea0e09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -424,6 +424,7 @@ dependencies = [ "astroport 5.5.0", "astroport-factory 1.9.0", "astroport-native-coin-registry 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "astroport-pair-concentrated", "astroport-pcl-common", "astroport-test", "cosmwasm-schema", @@ -435,6 +436,7 @@ dependencies = [ "itertools 0.12.1", "neutron-std", "neutron-test-tube 4.2.2-rc", + "semver", "serde", "thiserror", ] @@ -3242,9 +3244,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "serde" diff --git a/contracts/pair_concentrated_duality/Cargo.toml b/contracts/pair_concentrated_duality/Cargo.toml index 730b8ecdb..5217defb3 100644 --- a/contracts/pair_concentrated_duality/Cargo.toml +++ b/contracts/pair_concentrated_duality/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-pair-concentrated-duality" -version = "4.1.0" +version = "4.1.0" # must share the same version with the original PCL authors = ["Astroport"] edition = "2021" description = "The Astroport concentrated liquidity pair with Duality orderbook integration" @@ -35,17 +35,19 @@ itertools = { workspace = true } cw-utils = { workspace = true } cw2 = { workspace = true } -neutron-std = { git = "https://github.com/epanchee/neutron-std", branch = "backport/cosmwasm_v1" } - astroport = { path = "../../packages/astroport", version = "5.5", features = ["duality"] } astroport-pcl-common = { path = "../../packages/astroport_pcl_common", version = "2" } -neutron-test-tube = { git = "https://github.com/epanchee/neutron-test-tube-new", optional = true, branch = "feat/neutron-v5-cosmwasm-v1", version = "4.2.2-rc" } serde = { version = "1.0.203", features = ["derive"] } +semver = "1.0" + +neutron-std = { git = "https://github.com/epanchee/neutron-std", branch = "backport/cosmwasm_v1" } +neutron-test-tube = { git = "https://github.com/epanchee/neutron-test-tube-new", optional = true, branch = "feat/neutron-v5-cosmwasm-v1", version = "4.2.2-rc" } [dev-dependencies] -astroport-native-coin-registry = "1.1" +astroport-native-coin-registry = { version = "1.1", features = ["library"] } astroport-test = { path = "../../packages/astroport_test", version = "0.1.0" } -astroport-factory = { path = "../factory", version = "1.8" } +astroport-factory = { path = "../factory", version = "1.8", features = ["library"] } +astroport-pair-concentrated = { path = "../pair_concentrated", version = "4.1.0", features = ["library"] } anyhow = "1.0" derivative = "2.2" \ No newline at end of file diff --git a/contracts/pair_concentrated_duality/src/execute.rs b/contracts/pair_concentrated_duality/src/execute.rs index 9c882c56c..119115f9c 100644 --- a/contracts/pair_concentrated_duality/src/execute.rs +++ b/contracts/pair_concentrated_duality/src/execute.rs @@ -719,8 +719,9 @@ pub fn process_custom_msgs( let mut ob_state = OrderbookState::load(deps.storage)?; let cancel_orders_msgs = if let Some(false) = update_orderbook_conf.enable { + let msgs = ob_state.cancel_orders(&env.contract.address); ob_state.orders = vec![]; - ob_state.cancel_orders(&env.contract.address) + msgs } else { vec![] }; diff --git a/contracts/pair_concentrated_duality/src/instantiate.rs b/contracts/pair_concentrated_duality/src/instantiate.rs index 5b3170a3d..06206a3b4 100644 --- a/contracts/pair_concentrated_duality/src/instantiate.rs +++ b/contracts/pair_concentrated_duality/src/instantiate.rs @@ -1,17 +1,18 @@ +use cosmwasm_std::{ + ensure, from_json, Decimal256, DepsMut, Env, MessageInfo, Response, StdError, SubMsg, Uint128, +}; +use cw2::set_contract_version; + use astroport::pair::ReplyIds; use astroport::token_factory::tf_create_denom_msg; use astroport::{ - asset::PairInfo, factory::PairType, pair::InstantiateMsg, pair_concentrated::UpdatePoolParams, + asset::PairInfo, pair::InstantiateMsg, pair_concentrated::UpdatePoolParams, pair_concentrated_duality::ConcentratedDualityParams, }; use astroport_pcl_common::{ state::{AmpGamma, Config, PoolParams, PoolState, Precisions, PriceState}, utils::check_asset_infos, }; -use cosmwasm_std::{ - ensure, from_json, Decimal256, DepsMut, Env, MessageInfo, Response, StdError, SubMsg, Uint128, -}; -use cw2::set_contract_version; use crate::error::ContractError; use crate::orderbook::state::OrderbookState; @@ -116,7 +117,7 @@ pub fn instantiate( contract_addr: env.contract.address.clone(), liquidity_token: "".to_owned(), asset_infos: msg.asset_infos.clone(), - pair_type: PairType::Custom("concentrated_duality_orderbook".to_string()), + pair_type: msg.pair_type, }, factory_addr, block_time_last: env.block.time.seconds(), diff --git a/contracts/pair_concentrated_duality/src/migrate.rs b/contracts/pair_concentrated_duality/src/migrate.rs index eef5c875a..a2d25ceba 100644 --- a/contracts/pair_concentrated_duality/src/migrate.rs +++ b/contracts/pair_concentrated_duality/src/migrate.rs @@ -1,74 +1,69 @@ -use cosmwasm_std::{entry_point, DepsMut, Empty, Env, Response, StdResult}; +use cosmwasm_std::{ + attr, ensure, ensure_eq, entry_point, DepsMut, Env, Response, StdError, StdResult, +}; +use cw2::{get_contract_version, set_contract_version}; +use cw_storage_plus::Item; -// const MIGRATE_FROM: &str = "astroport-pair-concentrated"; -// const MIGRATION_VERSION: &str = "4.0.1"; +use astroport::factory::PairType; +use astroport::pair_concentrated_duality::MigrateMsg; +use astroport_pcl_common::state::Config; + +use crate::instantiate::{CONTRACT_NAME, CONTRACT_VERSION}; +use crate::orderbook::state::OrderbookState; +use crate::state::CONFIG; + +const MIGRATE_FROM: &str = "astroport-pair-concentrated"; +const VERSION_REQ: &str = ">=4.0.0, <5.0.0"; + +fn from_semver(err: semver::Error) -> StdError { + StdError::generic_err(format!("Semver: {err}")) +} /// Manages the contract migration. #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: Empty) -> StdResult { - todo!("add seamless migration from PCL to PCL with Duality integration") +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult { + let mut attrs = vec![]; + + let stored_info = get_contract_version(deps.storage)?; + match msg { + MigrateMsg::MigrateToOrderbook { orderbook_config } => { + let version_req = semver::VersionReq::parse(VERSION_REQ).map_err(from_semver)?; + let stored_ver = semver::Version::parse(&stored_info.version).map_err(from_semver)?; + ensure!( + stored_info.contract == MIGRATE_FROM && version_req.matches(&stored_ver), + StdError::generic_err(format!( + "Can migrate only from {MIGRATE_FROM} {VERSION_REQ}" + )) + ); + + let mut config: Config = Item::new("config").load(deps.storage)?; + let ob_state = OrderbookState::new(deps.api, orderbook_config)?; + config.pair_info.pair_type = + PairType::Custom("concentrated_duality_orderbook".to_string()); + CONFIG.save(deps.storage, &config)?; + + attrs.push(attr("action", "migrate_to_orderbook")); + + ob_state.save(deps.storage) + } + MigrateMsg::Migrate {} => { + ensure_eq!( + stored_info.contract, + CONTRACT_VERSION, + StdError::generic_err(format!("This endpoint is allowed only for {CONTRACT_NAME}")) + ); + + Err(StdError::generic_err("Not yet implemented".to_string()).into()) + } + }?; + + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - // let mut attrs = vec![]; - // - // let contract_info = CONTRACT.load(deps.storage)?; - // match msg { - // MigrateMsg::MigrateToOrderbook { params } => { - // if contract_info.contract != MIGRATE_FROM || contract_info.version != MIGRATION_VERSION - // { - // return Err(StdError::generic_err(format!( - // "Can't migrate from {} {}", - // contract_info.contract, contract_info.version - // ))); - // } - // - // let mut config: Config = Item::new("config").load(deps.storage)?; - // let base_precision = - // config.pair_info.asset_infos[0].decimals(&deps.querier, &config.factory_addr)?; - // let ob_state = OrderbookState::new( - // deps.querier, - // &env, - // ¶ms.market_id, - // params.orders_number, - // params.min_trades_to_avg, - // &config.pair_info.asset_infos, - // base_precision, - // )?; - // config.pair_info.pair_type = PairType::Custom("concentrated_inj_orderbook".to_string()); - // CONFIG.save(deps.storage, &config)?; - // ob_state.save(deps.storage)?; - // - // attrs.push(attr("action", "migrate_to_orderbook")); - // attrs.push(attr("subaccount_id", ob_state.subaccount.to_string())) - // } - // MigrateMsg::Migrate {} => { - // let contract_info = cw2::get_contract_version(deps.storage)?; - // match contract_info.contract.as_str() { - // CONTRACT_NAME => match contract_info.version.as_str() { - // "2.0.3" | "2.0.4" => {} - // _ => { - // return Err(StdError::generic_err(format!( - // "Can't migrate from {} {}", - // contract_info.contract, contract_info.version - // ))); - // } - // }, - // _ => { - // return Err(StdError::generic_err(format!( - // "Can't migrate from {} {}", - // contract_info.contract, contract_info.version - // ))); - // } - // } - // } - // } - // - // set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - // - // attrs.extend([ - // attr("previous_contract_name", contract_info.contract), - // attr("previous_contract_version", contract_info.version), - // attr("new_contract_name", CONTRACT_NAME), - // attr("new_contract_version", CONTRACT_VERSION), - // ]); - // Ok(Response::default().add_attributes(attrs)) + attrs.extend([ + attr("previous_contract_name", stored_info.contract), + attr("previous_contract_version", stored_info.version), + attr("new_contract_name", CONTRACT_NAME), + attr("new_contract_version", CONTRACT_VERSION), + ]); + Ok(Response::default().add_attributes(attrs)) } diff --git a/contracts/pair_concentrated_duality/src/orderbook/mod.rs b/contracts/pair_concentrated_duality/src/orderbook/mod.rs index 18b5e98a2..7313cd8ef 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/mod.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/mod.rs @@ -1,5 +1,5 @@ pub mod consts; -mod custom_types; +pub mod custom_types; pub mod error; pub mod execute; pub mod state; diff --git a/contracts/pair_concentrated_duality/tests/common/helper.rs b/contracts/pair_concentrated_duality/tests/common/helper.rs index 8374574bd..449909002 100644 --- a/contracts/pair_concentrated_duality/tests/common/helper.rs +++ b/contracts/pair_concentrated_duality/tests/common/helper.rs @@ -4,15 +4,16 @@ use std::collections::HashMap; use std::str::FromStr; -use anyhow::Result as AnyResult; +use anyhow::{anyhow, Result as AnyResult}; use cosmwasm_schema::cw_serde; use cosmwasm_std::testing::MockApi; use cosmwasm_std::{ - coin, coins, from_json, to_json_binary, Addr, BankMsg, Decimal, Decimal256, Empty, GovMsg, - IbcMsg, IbcQuery, MemoryStorage, StdError, StdResult, Uint128, + coin, coins, from_json, to_json_binary, to_json_vec, Addr, BankMsg, Decimal, Decimal256, Empty, + GovMsg, IbcMsg, IbcQuery, MemoryStorage, Querier, QueryRequest, StdError, StdResult, Uint128, }; use derivative::Derivative; use itertools::Itertools; +use neutron_std::types::neutron::dex::QueryAllLimitOrderTrancheUserByAddressRequest; use astroport::asset::{native_asset_info, Asset, AssetInfo, PairInfo}; use astroport::factory::{PairConfig, PairType}; @@ -25,8 +26,9 @@ use astroport::pair_concentrated::{ ConcentratedPoolConfig, ConcentratedPoolParams, ConcentratedPoolUpdateParams, QueryMsg, }; use astroport::pair_concentrated_duality::{ - ConcentratedDualityParams, DualityPairMsg, OrderbookConfig, + ConcentratedDualityParams, DualityPairMsg, OrderbookConfig, UpdateDualityOrderbook, }; +use astroport_pair_concentrated_duality::orderbook::custom_types::CustomQueryAllLimitOrderTrancheUserByAddressResponse; use astroport_pair_concentrated_duality::orderbook::state::OrderbookState; use astroport_pcl_common::state::Config; use astroport_test::coins::TestCoin; @@ -62,14 +64,26 @@ pub struct AmpGammaResponse { pub future_time: u64, } -fn pcl_duality_contract() -> Box> { +pub fn pcl_duality_contract() -> Box> { Box::new( ContractWrapper::new( astroport_pair_concentrated_duality::execute::execute, astroport_pair_concentrated_duality::instantiate::instantiate, astroport_pair_concentrated_duality::queries::query, ) - .with_reply_empty(astroport_pair_concentrated_duality::reply::reply), + .with_reply_empty(astroport_pair_concentrated_duality::reply::reply) + .with_migrate(astroport_pair_concentrated_duality::migrate::migrate), + ) +} + +pub fn pcl_contract() -> Box> { + Box::new( + ContractWrapper::new( + astroport_pair_concentrated::contract::execute, + astroport_pair_concentrated::contract::instantiate, + astroport_pair_concentrated::queries::query, + ) + .with_reply_empty(astroport_pair_concentrated::contract::reply), ) } @@ -124,6 +138,7 @@ impl Helper { owner: &Addr, test_coins: Vec, params: ConcentratedPoolParams, + with_orderbook: bool, ) -> AnyResult { let mut app = BasicAppBuilder::new() .with_stargate(NeutronStargate::default()) @@ -143,9 +158,13 @@ impl Helper { }) .collect::>(); - let pcl_code_id = app.store_code(pcl_duality_contract()); let factory_code_id = app.store_code(factory_contract()); - let pair_type = PairType::Custom("concentrated_duality_orderbook".to_string()); + + let pair_type = if with_orderbook { + PairType::Custom("concentrated_duality_orderbook".to_string()) + } else { + PairType::Custom("concentrated".to_string()) + }; let fake_maker = Addr::unchecked("fake_maker"); @@ -179,15 +198,26 @@ impl Helper { let init_msg = astroport::factory::InstantiateMsg { fee_address: Some(fake_maker.to_string()), - pair_configs: vec![PairConfig { - code_id: pcl_code_id, - maker_fee_bps: 5000, - total_fee_bps: 0u16, // Concentrated pair does not use this field, - pair_type: pair_type.clone(), - is_disabled: false, - is_generator_disabled: false, - permissioned: false, - }], + pair_configs: vec![ + PairConfig { + code_id: app.store_code(pcl_contract()), + maker_fee_bps: 5000, + total_fee_bps: 0u16, // Concentrated pair does not use this field, + pair_type: PairType::Custom("concentrated".to_string()), + is_disabled: false, + is_generator_disabled: false, + permissioned: false, + }, + PairConfig { + code_id: app.store_code(pcl_duality_contract()), + maker_fee_bps: 5000, + total_fee_bps: 0u16, // Concentrated pair does not use this field, + pair_type: PairType::Custom("concentrated_duality_orderbook".to_string()), + is_disabled: false, + is_generator_disabled: false, + permissioned: false, + }, + ], token_code_id: 0, generator_address: None, owner: owner.to_string(), @@ -215,7 +245,7 @@ impl Helper { astroport::factory::ExecuteMsg::CreatePair { pair_type, asset_infos: asset_infos.clone(), - init_params: Some( + init_params: Some(if with_orderbook { to_json_binary(&ConcentratedDualityParams { main_params: params, orderbook_config: OrderbookConfig { @@ -227,8 +257,10 @@ impl Helper { avg_price_adjustment: Decimal::from_str("0.001").unwrap(), }, }) - .unwrap(), - ), + .unwrap() + } else { + to_json_binary(¶ms).unwrap() + }), }; app.execute_contract(owner.clone(), factory.clone(), &init_pair_msg, &[])?; @@ -488,6 +520,49 @@ impl Helper { .map(|val| val.price) } + pub fn query_orders( + &self, + addr: impl Into, + ) -> AnyResult { + let query_msg = to_json_vec(&QueryRequest::::Stargate { + path: "/neutron.dex.Query/LimitOrderTrancheUserAllByAddress".to_string(), + data: QueryAllLimitOrderTrancheUserByAddressRequest { + address: addr.into(), + pagination: None, + } + .into(), + })?; + + let response_raw = self + .app + .raw_query(&query_msg) + .into_result() + .map_err(|err| anyhow!(err))? + .into_result() + .map_err(|err| anyhow!(err))?; + + from_json(&response_raw).map_err(Into::into) + } + + pub fn enable_orderbook(&mut self, enable: bool) -> AnyResult { + self.app.execute_contract( + self.owner.clone(), + self.pair_addr.clone(), + &ExecuteMsg::Custom(DualityPairMsg::UpdateOrderbookConfig( + UpdateDualityOrderbook { + enable: Some(enable), + executor: None, + remove_executor: false, + orders_number: None, + min_asset_0_order_size: None, + min_asset_1_order_size: None, + liquidity_percent: None, + }, + )), + &[], + ) + } + pub fn next_block(&mut self, time: u64) { self.app.update_block(|block| { block.time = block.time.plus_seconds(time); diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs index cc7a9d21b..46db060ac 100644 --- a/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs @@ -1,15 +1,20 @@ #![cfg(not(tarpaulin_include))] -use cosmwasm_std::{Addr, Decimal, Decimal256}; +use cosmwasm_std::{Addr, Decimal, Decimal256, Uint128}; +use cw2::set_contract_version; use itertools::{max, Itertools}; use astroport::asset::{native_asset_info, AssetInfoExt, MINIMUM_LIQUIDITY_AMOUNT}; use astroport::cosmwasm_ext::IntegerToDecimal; +use astroport::factory::PairType; use astroport::pair_concentrated::{ ConcentratedPoolParams, ConcentratedPoolUpdateParams, PromoteParams, UpdatePoolParams, }; -use astroport::pair_concentrated_duality::{DualityPairMsg, UpdateDualityOrderbook}; +use astroport::pair_concentrated_duality::{ + DualityPairMsg, MigrateMsg, OrderbookConfig, UpdateDualityOrderbook, +}; use astroport_pair_concentrated_duality::error::ContractError; +use astroport_pair_concentrated_duality::instantiate::{CONTRACT_NAME, CONTRACT_VERSION}; use astroport_pair_concentrated_duality::orderbook::error::OrderbookError; use astroport_pcl_common::consts::{AMP_MAX, AMP_MIN, MA_HALF_TIME_LIMITS}; use astroport_pcl_common::error::PclError; @@ -17,7 +22,7 @@ use astroport_test::coins::TestCoin; use astroport_test::convert::{dec_to_f64, f64_to_dec}; use astroport_test::cw_multi_test::Executor; -use crate::common::helper::{common_pcl_params, ExecuteMsg, Helper}; +use crate::common::helper::{common_pcl_params, pcl_duality_contract, ExecuteMsg, Helper}; mod common; @@ -34,6 +39,7 @@ fn check_wrong_initialization() { &owner, vec![TestCoin::native("untrn"), TestCoin::native("ASTRO")], wrong_params, + true, ) .unwrap_err(); @@ -53,6 +59,7 @@ fn check_wrong_initialization() { &owner, vec![TestCoin::native("untrn"), TestCoin::native("ASTRO")], wrong_params, + true, ) .unwrap_err(); @@ -72,6 +79,7 @@ fn check_wrong_initialization() { &owner, vec![TestCoin::native("untrn"), TestCoin::native("ASTRO")], wrong_params, + true, ) .unwrap_err(); @@ -85,6 +93,7 @@ fn check_wrong_initialization() { &owner, vec![TestCoin::native("untrn"), TestCoin::native("ASTRO")], params, + true, ) .unwrap(); } @@ -100,7 +109,7 @@ fn provide_and_withdraw() { ..common_pcl_params() }; - let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + let mut helper = Helper::new(&owner, test_coins.clone(), params, true).unwrap(); // checking LP token virtual price on an empty pool let lp_price = helper.query_lp_price().unwrap(); @@ -308,7 +317,7 @@ fn check_imbalanced_provide() { ..common_pcl_params() }; - let mut helper = Helper::new(&owner, test_coins.clone(), params.clone()).unwrap(); + let mut helper = Helper::new(&owner, test_coins.clone(), params.clone(), true).unwrap(); let user1 = Addr::unchecked("user1"); let assets = vec![ @@ -328,7 +337,7 @@ fn check_imbalanced_provide() { // creating a new pool with inverted price scale params.price_scale = Decimal::from_ratio(1u8, 2u8); - let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + let mut helper = Helper::new(&owner, test_coins.clone(), params, true).unwrap(); let assets = vec![ helper.assets[&test_coins[0]].with_balance(100_000_000000u128), @@ -354,7 +363,7 @@ fn provide_with_different_precision() { TestCoin::native("untrn"), ]; - let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params(), true).unwrap(); let assets = vec![ helper.assets[&test_coins[0]].with_balance(100_00000u128), @@ -403,7 +412,7 @@ fn swap_different_precisions() { TestCoin::native("untrn"), ]; - let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params(), true).unwrap(); let assets = vec![ helper.assets[&test_coins[0]].with_balance(100_000_00000u128), @@ -447,7 +456,7 @@ fn check_reverse_swap() { let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("uusd")]; - let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params(), true).unwrap(); let assets = vec![ helper.assets[&test_coins[0]].with_balance(100_000_000000u128), @@ -476,7 +485,7 @@ fn check_swaps_simple() { let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("uusd")]; - let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params(), true).unwrap(); let user = Addr::unchecked("user"); let offer_asset = helper.assets[&test_coins[0]].with_balance(100_000000u128); @@ -539,7 +548,7 @@ fn check_swaps_with_price_update() { let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("uusd")]; - let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params(), true).unwrap(); helper.next_block(1000); @@ -582,7 +591,7 @@ fn provides_and_swaps() { let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("uusd")]; - let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params(), true).unwrap(); helper.next_block(1000); @@ -632,7 +641,7 @@ fn check_amp_gamma_change() { gamma: f64_to_dec(0.0001), ..common_pcl_params() }; - let mut helper = Helper::new(&owner, test_coins, params).unwrap(); + let mut helper = Helper::new(&owner, test_coins, params, true).unwrap(); let random_user = Addr::unchecked("random"); let action = ConcentratedPoolUpdateParams::Update(UpdatePoolParams { @@ -717,7 +726,7 @@ fn check_prices() { let test_coins = vec![TestCoin::native("uusd"), TestCoin::native("usdx")]; - let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params(), true).unwrap(); helper.next_block(50_000); let check_prices = |helper: &Helper| { @@ -782,7 +791,7 @@ fn update_owner() { let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("uusd")]; - let mut helper = Helper::new(&owner, test_coins, common_pcl_params()).unwrap(); + let mut helper = Helper::new(&owner, test_coins, common_pcl_params(), true).unwrap(); let new_owner = String::from("new_owner"); @@ -869,7 +878,7 @@ fn check_orderbook_integration() { ..common_pcl_params() }; - let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + let mut helper = Helper::new(&owner, test_coins.clone(), params, true).unwrap(); let assets = vec![ helper.assets[&test_coins[0]].with_balance(1_000_000_000000u128), @@ -937,7 +946,7 @@ fn provide_withdraw_provide() { ..common_pcl_params() }; - let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + let mut helper = Helper::new(&owner, test_coins.clone(), params, true).unwrap(); let assets = vec![ helper.assets[&test_coins[0]].with_balance(10_938039u128), @@ -979,7 +988,7 @@ fn provide_withdraw_slippage() { ..common_pcl_params() }; - let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + let mut helper = Helper::new(&owner, test_coins.clone(), params, true).unwrap(); // Fully balanced provide let assets = vec![ @@ -1038,7 +1047,7 @@ fn check_small_trades() { ..common_pcl_params() }; - let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + let mut helper = Helper::new(&owner, test_coins.clone(), params, true).unwrap(); helper.give_me_money( &[ @@ -1111,3 +1120,291 @@ fn check_small_trades() { relative_diff ); } + +#[test] +fn test_migrate_cl_to_orderbook() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("untrn"), TestCoin::native("astro")]; + + let params = ConcentratedPoolParams { + price_scale: f64_to_dec(0.5), + ..common_pcl_params() + }; + let mut helper = Helper::new(&owner, test_coins.clone(), params, false).unwrap(); + + helper.give_me_money( + &[ + helper.assets[&test_coins[0]].with_balance(u128::MAX / 2), + helper.assets[&test_coins[1]].with_balance(u128::MAX / 2), + ], + &owner, + ); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(500_000e6 as u128), + helper.assets[&test_coins[1]].with_balance(1_000_000e6 as u128), + ]; + helper.provide_liquidity(&owner, &assets).unwrap(); + + // Make some swaps + for _ in 0..2 { + helper + .swap( + &owner, + &helper.assets[&test_coins[1]].with_balance(1000e6 as u128), + None, + ) + .unwrap(); + helper.next_block(1000); + helper + .swap( + &owner, + &helper.assets[&test_coins[0]].with_balance(500e6 as u128), + None, + ) + .unwrap(); + helper.next_block(1000); + } + + let orders_number = 5; + let migrate_msg = MigrateMsg::MigrateToOrderbook { + orderbook_config: OrderbookConfig { + liquidity_percent: Decimal::percent(20), + orders_number, + min_asset_0_order_size: Uint128::from(1000u128), + min_asset_1_order_size: Uint128::from(1000u128), + executor: Some(owner.to_string()), + avg_price_adjustment: f64_to_dec(0.001), + }, + }; + + let new_code_id = helper.app.store_code(pcl_duality_contract()); + + // Tweak PCL state to check migration errors + { + let mut contract_store = helper.app.contract_storage_mut(&helper.pair_addr); + set_contract_version(contract_store.as_mut(), "fake_pcl", CONTRACT_VERSION).unwrap() + }; + + let err = helper + .app + .migrate_contract( + owner.clone(), + helper.pair_addr.clone(), + &migrate_msg, + new_code_id, + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Can migrate only from astroport-pair-concentrated >=4.0.0, <5.0.0" + ); + + // Checking a major version higher than v4 + { + let mut contract_store = helper.app.contract_storage_mut(&helper.pair_addr); + set_contract_version( + contract_store.as_mut(), + "astroport-pair-concentrated", + "15.0.0", + ) + .unwrap() + }; + + let err = helper + .app + .migrate_contract( + owner.clone(), + helper.pair_addr.clone(), + &migrate_msg, + new_code_id, + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Can migrate only from astroport-pair-concentrated >=4.0.0, <5.0.0" + ); + + // Reverting to the correct version + { + let mut contract_store = helper.app.contract_storage_mut(&helper.pair_addr); + set_contract_version( + contract_store.as_mut(), + "astroport-pair-concentrated", + CONTRACT_VERSION, + ) + .unwrap() + }; + + // for RustRover linter otherwise it assumes that `CONTRACT_NAME` is unused + _ = CONTRACT_NAME; + // Try to use general migration path for ordinal PCL contract + let err = helper + .app + .migrate_contract( + owner.clone(), + helper.pair_addr.clone(), + &MigrateMsg::Migrate {}, + new_code_id, + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + format!("Generic error: This endpoint is allowed only for {CONTRACT_NAME}") + ); + + helper + .app + .migrate_contract( + owner.clone(), + helper.pair_addr.clone(), + &migrate_msg, + new_code_id, + ) + .unwrap(); + + let config = helper.query_config().unwrap(); + assert_eq!( + config.pair_info.pair_type, + PairType::Custom("concentrated_duality_orderbook".to_string()) + ); + assert_eq!(config.pool_state.price_state.price_scale.to_string(), "0.5"); + let ob_state = helper.query_ob_config().unwrap(); + assert!(!ob_state.enabled, "Must be disabled by default"); + + // Cant perform PCL transformation again + let err = helper + .app + .migrate_contract( + owner.clone(), + helper.pair_addr.clone(), + &migrate_msg, + new_code_id, + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Can migrate only from astroport-pair-concentrated >=4.0.0, <5.0.0" + ); + + for _ in 0..3 { + helper + .swap( + &owner, + &helper.assets[&test_coins[1]].with_balance(1000e6 as u128), + None, + ) + .unwrap(); + helper.next_block(1000); + helper + .swap( + &owner, + &helper.assets[&test_coins[0]].with_balance(500e6 as u128), + None, + ) + .unwrap(); + helper.next_block(1000); + } + + // Confirm that PCL is not posting anything to the orderbook + // Zero orders since OB integration is disabled + assert_eq!( + helper + .query_orders(&helper.pair_addr) + .unwrap() + .limit_orders + .len(), + 0 + ); + + // Enable orderbook + helper.enable_orderbook(true).unwrap(); + + // Still zero + assert_eq!( + helper + .query_orders(&helper.pair_addr) + .unwrap() + .limit_orders + .len(), + 0 + ); + + // Perform swaps to trigger orders placement + for _ in 0..3 { + helper + .swap( + &owner, + &helper.assets[&test_coins[1]].with_balance(1000e6 as u128), + None, + ) + .unwrap(); + helper.next_block(1000); + helper + .swap( + &owner, + &helper.assets[&test_coins[0]].with_balance(500e6 as u128), + None, + ) + .unwrap(); + helper.next_block(1000); + } + + assert_eq!( + helper + .query_orders(&helper.pair_addr) + .unwrap() + .limit_orders + .len(), + (orders_number * 2) as usize + ); + + // Disable orderbook + helper.enable_orderbook(false).unwrap(); + + assert_eq!( + helper + .query_orders(&helper.pair_addr) + .unwrap() + .limit_orders + .len(), + 0 + ); + + // Enable again + helper.enable_orderbook(true).unwrap(); + + // Trigger order placement + helper + .swap( + &owner, + &helper.assets[&test_coins[1]].with_balance(1000e6 as u128), + None, + ) + .unwrap(); + + assert_eq!( + helper + .query_orders(&helper.pair_addr) + .unwrap() + .limit_orders + .len(), + 10 + ); + + // Withdraw all liquidity + let lp_amount = helper.native_balance(&helper.lp_token, &owner); + helper + .withdraw_liquidity(&owner, lp_amount, vec![]) + .unwrap(); + + assert_eq!( + helper + .query_orders(&helper.pair_addr) + .unwrap() + .limit_orders + .len(), + 0 + ); +} diff --git a/packages/astroport/src/pair_concentrated_duality.rs b/packages/astroport/src/pair_concentrated_duality.rs index 79922ea90..e2c646c79 100644 --- a/packages/astroport/src/pair_concentrated_duality.rs +++ b/packages/astroport/src/pair_concentrated_duality.rs @@ -75,3 +75,11 @@ impl TryFrom for ReplyIds { } } } + +#[cw_serde] +pub enum MigrateMsg { + /// Migration from plain PCL to PCL with Duality integration + MigrateToOrderbook { orderbook_config: OrderbookConfig }, + /// General migration for `astroport-pair-concentrated-duality` pool + Migrate {}, +} diff --git a/packages/astroport_test/src/modules/neutron_stargate.rs b/packages/astroport_test/src/modules/neutron_stargate.rs index 68eea0868..88e8051f8 100644 --- a/packages/astroport_test/src/modules/neutron_stargate.rs +++ b/packages/astroport_test/src/modules/neutron_stargate.rs @@ -12,7 +12,7 @@ use cw_multi_test::{ }; use itertools::Itertools; use neutron_std::types::neutron::dex::{ - LimitOrderTrancheUser, MsgCancelLimitOrderResponse, MsgPlaceLimitOrder, + LimitOrderTrancheUser, MsgCancelLimitOrder, MsgCancelLimitOrderResponse, MsgPlaceLimitOrder, QueryAllLimitOrderTrancheUserByAddressRequest, QueryAllLimitOrderTrancheUserByAddressResponse, QuerySimulateCancelLimitOrderRequest, QuerySimulateCancelLimitOrderResponse, }; @@ -29,6 +29,11 @@ pub struct NeutronStargate { orders: RefCell>>, } +impl NeutronStargate { + const ESCROW_ADDR: &'static str = + "cosmwasm1ypz7dakxd9umutjtxpk7md3ja5shk84qlj7cv0f6yqkj2naef00q4rdsps"; +} + impl Stargate for NeutronStargate {} impl Module for NeutronStargate { @@ -106,6 +111,14 @@ impl Module for NeutronStargate { MsgPlaceLimitOrder::TYPE_URL => { let tranche_key = format!("{:x}", sha2::Sha256::digest(&value)); + // Escrow tokens + let value: MsgPlaceLimitOrder = value.try_into()?; + let bank_msg = BankMsg::Send { + to_address: Self::ESCROW_ADDR.to_string(), + amount: vec![coin(value.amount_in.parse().unwrap(), &value.token_in)], + }; + router.execute(api, storage, block, sender.clone(), bank_msg.into())?; + self.orders .borrow_mut() .entry(sender.to_string()) @@ -113,6 +126,28 @@ impl Module for NeutronStargate { .insert(tranche_key, value.try_into()?); Ok(AppResponse::default()) } + MsgCancelLimitOrder::TYPE_URL => { + let cancel_msg: MsgCancelLimitOrder = value.try_into()?; + let order = self + .orders + .borrow_mut() + .get_mut(&sender.to_string()) + .and_then(|m| m.remove(&cancel_msg.tranche_key)) + .ok_or_else(|| anyhow::anyhow!("Order not found"))?; + + // Unescrow tokens + let msg = BankMsg::Send { + to_address: sender.to_string(), + amount: vec![coin(order.amount_in.parse().unwrap(), &order.token_in)], + }; + router.execute( + api, + storage, + block, + Addr::unchecked(Self::ESCROW_ADDR), + msg.into(), + ) + } _ => Err(anyhow::anyhow!( "Unexpected exec msg {type_url} from {sender:?}", )), From 592dc920aad279fa46486facee2d297a7ce576f0 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Thu, 6 Feb 2025 20:01:38 +0300 Subject: [PATCH 14/24] update Cargo.lock --- Cargo.lock | 48 +++++++++++++------ .../pair_concentrated_duality/Cargo.toml | 2 +- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3fea0e09..ce2851255 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,7 +151,7 @@ dependencies = [ [[package]] name = "astroport" -version = "5.5.0" +version = "5.7.0" dependencies = [ "astroport-circular-buffer 0.2.0", "cosmos-sdk-proto 0.19.0", @@ -212,7 +212,7 @@ name = "astroport-factory" version = "1.9.0" dependencies = [ "anyhow", - "astroport 5.5.0", + "astroport 5.7.0", "astroport-pair 2.1.0", "astroport-test", "cosmwasm-schema", @@ -257,11 +257,11 @@ dependencies = [ [[package]] name = "astroport-incentives" -version = "1.2.0" +version = "1.3.0" dependencies = [ "anyhow", "astro-token-converter", - "astroport 5.5.0", + "astroport 5.7.0", "astroport-factory 1.9.0", "astroport-native-coin-registry 1.1.0", "astroport-pair 2.1.0", @@ -372,7 +372,7 @@ dependencies = [ name = "astroport-pair" version = "2.1.0" dependencies = [ - "astroport 5.5.0", + "astroport 5.7.0", "astroport-factory 1.9.0", "astroport-incentives", "astroport-test", @@ -395,7 +395,7 @@ name = "astroport-pair-concentrated" version = "4.1.0" dependencies = [ "anyhow", - "astroport 5.5.0", + "astroport 5.7.0", "astroport-circular-buffer 0.2.0", "astroport-factory 1.9.0", "astroport-incentives", @@ -421,7 +421,7 @@ name = "astroport-pair-concentrated-duality" version = "4.1.0" dependencies = [ "anyhow", - "astroport 5.5.0", + "astroport 5.7.0", "astroport-factory 1.9.0", "astroport-native-coin-registry 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "astroport-pair-concentrated", @@ -469,7 +469,7 @@ name = "astroport-pair-stable" version = "4.1.0" dependencies = [ "anyhow", - "astroport 5.5.0", + "astroport 5.7.0", "astroport-circular-buffer 0.2.0", "astroport-factory 1.9.0", "astroport-incentives", @@ -495,7 +495,7 @@ name = "astroport-pair-transmuter" version = "1.1.2" dependencies = [ "anyhow", - "astroport 5.5.0", + "astroport 5.7.0", "astroport-factory 1.9.0", "astroport-native-coin-registry 1.1.0", "astroport-test", @@ -511,11 +511,30 @@ dependencies = [ "thiserror", ] +[[package]] +name = "astroport-pair-xastro" +version = "1.0.0" +dependencies = [ + "anyhow", + "astroport 5.7.0", + "astroport-factory 1.9.0", + "astroport-staking", + "astroport-test", + "astroport-tokenfactory-tracker 1.0.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "derivative", + "itertools 0.12.1", + "thiserror", +] + [[package]] name = "astroport-pair-xyk-sale-tax" version = "2.1.0" dependencies = [ - "astroport 5.5.0", + "astroport 5.7.0", "astroport-factory 1.9.0", "astroport-incentives", "astroport-pair 1.3.3", @@ -541,7 +560,7 @@ name = "astroport-pcl-common" version = "2.1.0" dependencies = [ "anyhow", - "astroport 5.5.0", + "astroport 5.7.0", "astroport-factory 1.9.0", "astroport-test", "cosmwasm-schema", @@ -573,11 +592,11 @@ dependencies = [ [[package]] name = "astroport-staking" -version = "2.2.0" +version = "2.3.0" dependencies = [ "anyhow", "astroport 4.0.3", - "astroport 5.5.0", + "astroport 5.7.0", "astroport-tokenfactory-tracker 1.0.0", "cosmwasm-schema", "cosmwasm-std", @@ -595,7 +614,7 @@ name = "astroport-test" version = "0.1.0" dependencies = [ "anyhow", - "astroport 5.5.0", + "astroport 5.7.0", "astroport-factory 1.9.0", "cosmwasm-schema", "cosmwasm-std", @@ -605,6 +624,7 @@ dependencies = [ "cw20-base 1.1.2", "itertools 0.12.1", "neutron-std", + "osmosis-std", "prost 0.12.6", "schemars", "serde", diff --git a/contracts/pair_concentrated_duality/Cargo.toml b/contracts/pair_concentrated_duality/Cargo.toml index 5217defb3..661899ccf 100644 --- a/contracts/pair_concentrated_duality/Cargo.toml +++ b/contracts/pair_concentrated_duality/Cargo.toml @@ -35,7 +35,7 @@ itertools = { workspace = true } cw-utils = { workspace = true } cw2 = { workspace = true } -astroport = { path = "../../packages/astroport", version = "5.5", features = ["duality"] } +astroport = { path = "../../packages/astroport", version = "5.7", features = ["duality"] } astroport-pcl-common = { path = "../../packages/astroport_pcl_common", version = "2" } serde = { version = "1.0.203", features = ["derive"] } From 5c5ca9c9921921ad03201123d84bf06d559afe3d Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Thu, 6 Feb 2025 20:29:22 +0300 Subject: [PATCH 15/24] add general PCL tests --- .../pair_concentrated_duality/src/execute.rs | 2 +- .../tests/common/helper.rs | 26 ++ .../tests/pcl_duality_integration.rs | 294 +++++++++++++++++- 3 files changed, 318 insertions(+), 4 deletions(-) diff --git a/contracts/pair_concentrated_duality/src/execute.rs b/contracts/pair_concentrated_duality/src/execute.rs index 119115f9c..894fdf13a 100644 --- a/contracts/pair_concentrated_duality/src/execute.rs +++ b/contracts/pair_concentrated_duality/src/execute.rs @@ -671,7 +671,7 @@ fn update_config( // Ensure the fee share isn't 0 and doesn't exceed the maximum allowed value ensure!( - (0..MAX_FEE_SHARE_BPS).contains(&fee_share_bps), + (1..=MAX_FEE_SHARE_BPS).contains(&fee_share_bps), ContractError::FeeShareOutOfBounds {} ); diff --git a/contracts/pair_concentrated_duality/tests/common/helper.rs b/contracts/pair_concentrated_duality/tests/common/helper.rs index 449909002..9cd0b0df8 100644 --- a/contracts/pair_concentrated_duality/tests/common/helper.rs +++ b/contracts/pair_concentrated_duality/tests/common/helper.rs @@ -313,6 +313,32 @@ impl Helper { .execute_contract(sender.clone(), self.pair_addr.clone(), &msg, &funds) } + pub fn provide_liquidity_full( + &mut self, + sender: &Addr, + assets: &[Asset], + slippage_tolerance: Option, + auto_stake: Option, + receiver: Option, + min_lp_to_receive: Option, + ) -> AnyResult { + let funds = assets + .iter() + .map(|asset| asset.as_coin().unwrap()) + .collect_vec(); + + let msg = ExecuteMsg::ProvideLiquidity { + assets: assets.to_vec(), + slippage_tolerance, + auto_stake, + receiver, + min_lp_to_receive, + }; + + self.app + .execute_contract(sender.clone(), self.pair_addr.clone(), &msg, &funds) + } + pub fn withdraw_liquidity( &mut self, sender: &Addr, diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs index 46db060ac..4397e0fae 100644 --- a/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs @@ -1,12 +1,15 @@ #![cfg(not(tarpaulin_include))] -use cosmwasm_std::{Addr, Decimal, Decimal256, Uint128}; +use std::str::FromStr; + +use cosmwasm_std::{Addr, Decimal, Decimal256, StdError, Uint128}; use cw2::set_contract_version; use itertools::{max, Itertools}; use astroport::asset::{native_asset_info, AssetInfoExt, MINIMUM_LIQUIDITY_AMOUNT}; use astroport::cosmwasm_ext::IntegerToDecimal; use astroport::factory::PairType; +use astroport::pair::{QueryMsg, MAX_FEE_SHARE_BPS}; use astroport::pair_concentrated::{ ConcentratedPoolParams, ConcentratedPoolUpdateParams, PromoteParams, UpdatePoolParams, }; @@ -98,6 +101,24 @@ fn check_wrong_initialization() { .unwrap(); } +#[test] +fn check_create_pair_with_unsupported_denom() { + let owner = Addr::unchecked("owner"); + + let wrong_coins = vec![TestCoin::native("rc"), TestCoin::native("uusdc")]; + let valid_coins = vec![TestCoin::native("uluna"), TestCoin::native("uusdc")]; + + let params = common_pcl_params(); + + let err = Helper::new(&owner, wrong_coins.clone(), params.clone(), true).unwrap_err(); + assert_eq!( + "Generic error: Invalid denom length [3,128]: rc", + err.root_cause().to_string() + ); + + Helper::new(&owner, valid_coins.clone(), params.clone(), true).unwrap(); +} + #[test] fn provide_and_withdraw() { let owner = Addr::unchecked("owner"); @@ -450,6 +471,71 @@ fn swap_different_precisions() { ); } +#[test] +fn simulate_provide() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("uluna"), TestCoin::native("untrn")]; + + let params = ConcentratedPoolParams { + price_scale: Decimal::from_ratio(2u8, 1u8), + ..common_pcl_params() + }; + + let mut helper = Helper::new(&owner, test_coins.clone(), params, true).unwrap(); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_000000u128), + helper.assets[&test_coins[1]].with_balance(50_000_000000u128), + ]; + + let user1 = Addr::unchecked("user1"); + + let shares: Uint128 = helper + .app + .wrap() + .query_wasm_smart( + helper.pair_addr.to_string(), + &QueryMsg::SimulateProvide { + assets: assets.clone(), + slippage_tolerance: None, + }, + ) + .unwrap(); + + helper.give_me_money(&assets, &user1); + helper.provide_liquidity(&user1, &assets).unwrap(); + + assert_eq!( + shares.u128(), + helper.native_balance(&helper.lp_token, &user1) + ); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_0000u128), + helper.assets[&test_coins[1]].with_balance(50_000_000000u128), + ]; + + let err = helper + .app + .wrap() + .query_wasm_smart::( + helper.pair_addr.to_string(), + &QueryMsg::SimulateProvide { + assets: assets.clone(), + slippage_tolerance: Option::from(Decimal::percent(1)), + }, + ) + .unwrap_err(); + + assert_eq!( + err, + StdError::generic_err( + "Querier contract error: Generic error: Operation exceeds max spread limit" + ) + ); +} + #[test] fn check_reverse_swap() { let owner = Addr::unchecked("owner"); @@ -851,6 +937,39 @@ fn update_owner() { .unwrap_err(); assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); + // Drop ownership proposal + let err = helper + .app + .execute_contract( + Addr::unchecked("invalid_addr"), + helper.pair_addr.clone(), + &ExecuteMsg::DropOwnershipProposal {}, + &[], + ) + .unwrap_err(); + assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); + + helper + .app + .execute_contract( + helper.owner.clone(), + helper.pair_addr.clone(), + &ExecuteMsg::DropOwnershipProposal {}, + &[], + ) + .unwrap(); + + // Propose new owner + helper + .app + .execute_contract( + Addr::unchecked(&helper.owner), + helper.pair_addr.clone(), + &msg, + &[], + ) + .unwrap(); + // Claim ownership helper .app @@ -867,7 +986,23 @@ fn update_owner() { } #[test] -fn check_orderbook_integration() { +fn query_d_test() { + let owner = Addr::unchecked("owner"); + let test_coins = vec![TestCoin::native("uusd"), TestCoin::native("untrn")]; + + // create pair with test_coins + let helper = Helper::new(&owner, test_coins.clone(), common_pcl_params(), true).unwrap(); + + // query current pool D value before providing any liquidity + let err = helper.query_d().unwrap_err(); + assert_eq!( + err.to_string(), + "Generic error: Querier contract error: Generic error: Pools are empty" + ); +} + +#[test] +fn asset_balances_tracking_with_in_params() { let owner = Addr::unchecked("owner"); let test_coins = vec![TestCoin::native("uusd"), TestCoin::native("untrn")]; @@ -1011,7 +1146,7 @@ fn provide_withdraw_slippage() { .unwrap_err(); assert_eq!( ContractError::PclError(PclError::MaxSpreadAssertion {}), - err.downcast().unwrap() + err.downcast().unwrap(), ); // With 3% slippage it should work helper @@ -1034,6 +1169,159 @@ fn provide_withdraw_slippage() { helper .provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.5))) .unwrap(); + + helper.give_me_money(&assets, &owner); + let err = helper + .provide_liquidity_full( + &owner, + &assets, + Some(f64_to_dec(0.5)), + None, + None, + Some(10000000000u128.into()), + ) + .unwrap_err(); + assert_eq!( + ContractError::ProvideSlippageViolation(1000229863u128.into(), 10000000000u128.into()), + err.downcast().unwrap(), + ); + + helper + .provide_liquidity_full( + &owner, + &assets, + Some(f64_to_dec(0.5)), + None, + None, + Some(1000229863u128.into()), + ) + .unwrap(); +} + +#[test] +fn check_correct_fee_share() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("uluna"), TestCoin::native("uusdc")]; + + let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params(), true).unwrap(); + + let share_recipient = Addr::unchecked("share_recipient"); + // Attempt setting fee share with max+1 fee share + let action = ConcentratedPoolUpdateParams::EnableFeeShare { + fee_share_bps: MAX_FEE_SHARE_BPS + 1, + fee_share_address: share_recipient.to_string(), + }; + let err = helper.update_config(&owner, &action).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::FeeShareOutOfBounds {} + ); + + let action = ConcentratedPoolUpdateParams::EnableFeeShare { + fee_share_bps: 0, + fee_share_address: share_recipient.to_string(), + }; + let err = helper.update_config(&owner, &action).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::FeeShareOutOfBounds {} + ); + + helper.next_block(1000); + + // Set to 5% fee share + let action = ConcentratedPoolUpdateParams::EnableFeeShare { + fee_share_bps: 1000, + fee_share_address: share_recipient.to_string(), + }; + helper.update_config(&owner, &action).unwrap(); + + let config = helper.query_config().unwrap(); + let fee_share = config.fee_share.unwrap(); + assert_eq!(fee_share.bps, 1000u16); + assert_eq!(fee_share.recipient, share_recipient.to_string()); + + helper.next_block(1000); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_000000u128), + helper.assets[&test_coins[1]].with_balance(100_000_000000u128), + ]; + helper.give_me_money(&assets, &owner); + helper.provide_liquidity(&owner, &assets).unwrap(); + + helper.next_block(1000); + + let user = Addr::unchecked("user"); + let offer_asset = helper.assets[&test_coins[0]].with_balance(100_000000u128); + helper.give_me_money(&[offer_asset.clone()], &user); + helper.swap(&user, &offer_asset, None).unwrap(); + + let last_price = helper + .query_config() + .unwrap() + .pool_state + .price_state + .last_price; + assert_eq!( + last_price, + Decimal256::from_str("1.001187607454013938").unwrap() + ); + + // Check that the shared fees are sent + let expected_fee_share = 26081u128; + let recipient_balance = helper.coin_balance(&test_coins[1], &share_recipient); + assert_eq!(recipient_balance, expected_fee_share); + + let provider = Addr::unchecked("provider"); + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(1_000_000000u128), + helper.assets[&test_coins[1]].with_balance(1_000_000000u128), + ]; + helper.give_me_money(&assets, &provider); + helper.provide_liquidity(&provider, &assets).unwrap(); + + let offer_asset = helper.assets[&test_coins[1]].with_balance(100_000000u128); + helper.give_me_money(&[offer_asset.clone()], &user); + helper.swap(&user, &offer_asset, None).unwrap(); + + let last_price = helper + .query_config() + .unwrap() + .pool_state + .price_state + .last_price; + assert_eq!( + last_price, + Decimal256::from_str("0.998842355796925899").unwrap() + ); + + helper + .withdraw_liquidity(&provider, 999_999354, vec![]) + .unwrap(); + + let offer_asset = helper.assets[&test_coins[0]].with_balance(100_000000u128); + helper.give_me_money(&[offer_asset.clone()], &user); + helper.swap(&user, &offer_asset, None).unwrap(); + + let last_price = helper + .query_config() + .unwrap() + .pool_state + .price_state + .last_price; + assert_eq!( + last_price, + Decimal256::from_str("1.00118760696709103").unwrap() + ); + + // Disable fee share + let action = ConcentratedPoolUpdateParams::DisableFeeShare {}; + helper.update_config(&owner, &action).unwrap(); + + let config = helper.query_config().unwrap(); + assert!(config.fee_share.is_none()); } #[test] From b98ec4bd8ffd0e1b87ad077c03b0a20e868de3ed Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Thu, 6 Feb 2025 20:44:52 +0300 Subject: [PATCH 16/24] fix migration --- .../pair_concentrated_duality/src/migrate.rs | 2 +- .../tests/pcl_duality_integration.rs | 39 ++++++++----------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/contracts/pair_concentrated_duality/src/migrate.rs b/contracts/pair_concentrated_duality/src/migrate.rs index a2d25ceba..544bd7271 100644 --- a/contracts/pair_concentrated_duality/src/migrate.rs +++ b/contracts/pair_concentrated_duality/src/migrate.rs @@ -49,7 +49,7 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult MigrateMsg::Migrate {} => { ensure_eq!( stored_info.contract, - CONTRACT_VERSION, + CONTRACT_NAME, StdError::generic_err(format!("This endpoint is allowed only for {CONTRACT_NAME}")) ); diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs index 4397e0fae..5aef17892 100644 --- a/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs @@ -1002,7 +1002,7 @@ fn query_d_test() { } #[test] -fn asset_balances_tracking_with_in_params() { +fn test_ob_integration() { let owner = Addr::unchecked("owner"); let test_coins = vec![TestCoin::native("uusd"), TestCoin::native("untrn")]; @@ -1022,25 +1022,7 @@ fn asset_balances_tracking_with_in_params() { helper.give_me_money(&assets, &owner); helper.provide_liquidity(&owner, &assets).unwrap(); - helper - .app - .execute_contract( - owner.clone(), - helper.pair_addr.clone(), - &ExecuteMsg::Custom(DualityPairMsg::UpdateOrderbookConfig( - UpdateDualityOrderbook { - enable: Some(true), - executor: None, - remove_executor: false, - orders_number: None, - min_asset_0_order_size: None, - min_asset_1_order_size: None, - liquidity_percent: None, - }, - )), - &[], - ) - .unwrap(); + helper.enable_orderbook(true).unwrap(); let err = helper .app @@ -1065,8 +1047,6 @@ fn asset_balances_tracking_with_in_params() { let ob_config = helper.query_ob_config().unwrap(); assert_eq!(ob_config.orders.len() as u8, ob_config.orders_number * 2); - - // TODO: test orderbook disabling } #[test] @@ -1552,6 +1532,21 @@ fn test_migrate_cl_to_orderbook() { ) .unwrap(); + // Try to use general migration path; Currently is not implemented + let err = helper + .app + .migrate_contract( + owner.clone(), + helper.pair_addr.clone(), + &MigrateMsg::Migrate {}, + new_code_id, + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Not yet implemented" + ); + let config = helper.query_config().unwrap(); assert_eq!( config.pair_info.pair_type, From 5063f1294f9140c7908f352c527813adde494b30 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Thu, 6 Feb 2025 21:12:10 +0300 Subject: [PATCH 17/24] increase coverage --- .../pair_concentrated_duality/src/utils.rs | 16 ++- .../tests/common/helper.rs | 14 ++- .../tests/pcl_duality_integration.rs | 102 +++++++++++++++++- 3 files changed, 126 insertions(+), 6 deletions(-) diff --git a/contracts/pair_concentrated_duality/src/utils.rs b/contracts/pair_concentrated_duality/src/utils.rs index 20d0a79b3..6f2802c0c 100644 --- a/contracts/pair_concentrated_duality/src/utils.rs +++ b/contracts/pair_concentrated_duality/src/utils.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - ensure, Decimal, Decimal256, Deps, Env, QuerierWrapper, StdError, StdResult, Uint128, + ensure, ensure_eq, Decimal, Decimal256, Deps, Env, QuerierWrapper, StdError, StdResult, Uint128, }; use itertools::Itertools; @@ -175,14 +175,24 @@ pub fn ensure_min_assets_to_receive( min_assets_to_receive: Option>, ) -> Result<(), ContractError> { if let Some(mut min_assets_to_receive) = min_assets_to_receive { - ensure!( - min_assets_to_receive.len() == min_assets_to_receive.len(), + ensure_eq!( + min_assets_to_receive.len(), + refund_assets.len(), ContractError::WrongAssetLength { expected: refund_assets.len(), actual: min_assets_to_receive.len(), } ); + // Ensure unique + ensure!( + min_assets_to_receive + .iter() + .map(|asset| &asset.info) + .all_unique(), + StdError::generic_err("Duplicated assets in min_assets_to_receive") + ); + for asset in &min_assets_to_receive { ensure!( config.pair_info.asset_infos.contains(&asset.info), diff --git a/contracts/pair_concentrated_duality/tests/common/helper.rs b/contracts/pair_concentrated_duality/tests/common/helper.rs index 9cd0b0df8..8a59d9667 100644 --- a/contracts/pair_concentrated_duality/tests/common/helper.rs +++ b/contracts/pair_concentrated_duality/tests/common/helper.rs @@ -339,23 +339,33 @@ impl Helper { .execute_contract(sender.clone(), self.pair_addr.clone(), &msg, &funds) } - pub fn withdraw_liquidity( + pub fn withdraw_liquidity_full( &mut self, sender: &Addr, amount: u128, assets: Vec, + min_assets_to_receive: Option>, ) -> AnyResult { self.app.execute_contract( sender.clone(), self.pair_addr.clone(), &ExecuteMsg::WithdrawLiquidity { assets, - min_assets_to_receive: None, + min_assets_to_receive, }, &[coin(amount, self.lp_token.to_string())], ) } + pub fn withdraw_liquidity( + &mut self, + sender: &Addr, + amount: u128, + assets: Vec, + ) -> AnyResult { + self.withdraw_liquidity_full(sender, amount, assets, None) + } + pub fn swap( &mut self, sender: &Addr, diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs index 5aef17892..4a339af60 100644 --- a/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs @@ -6,7 +6,7 @@ use cosmwasm_std::{Addr, Decimal, Decimal256, StdError, Uint128}; use cw2::set_contract_version; use itertools::{max, Itertools}; -use astroport::asset::{native_asset_info, AssetInfoExt, MINIMUM_LIQUIDITY_AMOUNT}; +use astroport::asset::{native_asset_info, Asset, AssetInfoExt, MINIMUM_LIQUIDITY_AMOUNT}; use astroport::cosmwasm_ext::IntegerToDecimal; use astroport::factory::PairType; use astroport::pair::{QueryMsg, MAX_FEE_SHARE_BPS}; @@ -325,6 +325,106 @@ fn provide_and_withdraw() { ); assert_eq!(46910_055478, helper.coin_balance(&test_coins[0], &user2)); assert_eq!(26653_440612, helper.coin_balance(&test_coins[1], &user2)); + + let err = helper + .withdraw_liquidity_full( + &user2, + 10_000_000000, + vec![], + Some(vec![helper.assets[&test_coins[0]].with_balance(0u128)]), + ) + .unwrap_err(); + assert_eq!( + ContractError::WrongAssetLength { + expected: 2, + actual: 1 + }, + err.downcast().unwrap() + ); + + let err = helper + .withdraw_liquidity_full( + &user2, + 10_000_000000, + vec![], + Some(vec![ + Asset::native("random", 100u128), + Asset::native("random2", 100u128), + ]), + ) + .unwrap_err(); + assert_eq!( + ContractError::InvalidAsset("random".to_string()), + err.downcast().unwrap() + ); + + let err = helper + .withdraw_liquidity_full( + &user2, + 10_000_000000, + vec![], + Some(vec![ + helper.assets[&test_coins[0]].with_balance(0u128), + helper.assets[&test_coins[0]].with_balance(0u128), + ]), + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Duplicated assets in min_assets_to_receive" + ); + + let err = helper + .withdraw_liquidity_full( + &user2, + 10_000_000000, + vec![], + Some(vec![ + helper.assets[&test_coins[1]].with_balance(100_000_0000000u128), + helper.assets[&test_coins[0]].with_balance(0u128), + ]), + ) + .unwrap_err(); + assert_eq!( + ContractError::WithdrawSlippageViolation { + asset_name: helper.assets[&test_coins[1]].to_string(), + received: 7538731439u128.into(), + expected: 100_000_0000000u128.into(), + }, + err.downcast().unwrap() + ); + + let err = helper + .withdraw_liquidity_full( + &user2, + 10_000_000000, + vec![], + Some(vec![ + helper.assets[&test_coins[1]].with_balance(7538731439u128), + helper.assets[&test_coins[0]].with_balance(100_000_0000000u128), + ]), + ) + .unwrap_err(); + assert_eq!( + ContractError::WithdrawSlippageViolation { + asset_name: helper.assets[&test_coins[0]].to_string(), + received: 13268167332u128.into(), + expected: 100_000_0000000u128.into(), + }, + err.downcast().unwrap() + ); + + helper + .withdraw_liquidity_full( + &user2, + 10_000_000000, + vec![], + Some(vec![ + helper.assets[&test_coins[1]].with_balance(7538731439u128), + helper.assets[&test_coins[0]].with_balance(13268167332u128), + ]), + ) + .unwrap(); } #[test] From 1ade2d6238032bebcce5866033088007fe95bbcf Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Thu, 6 Feb 2025 21:15:11 +0300 Subject: [PATCH 18/24] remove debug strings --- .../pair_concentrated_duality/src/execute.rs | 18 +++--------------- .../src/orderbook/execute.rs | 6 +----- .../src/orderbook/state.rs | 4 +--- .../src/orderbook/utils.rs | 18 ++---------------- .../tests/pcl_duality_integration.rs | 4 +--- 5 files changed, 8 insertions(+), 42 deletions(-) diff --git a/contracts/pair_concentrated_duality/src/execute.rs b/contracts/pair_concentrated_duality/src/execute.rs index 894fdf13a..5330e38d5 100644 --- a/contracts/pair_concentrated_duality/src/execute.rs +++ b/contracts/pair_concentrated_duality/src/execute.rs @@ -170,10 +170,6 @@ pub fn provide_liquidity( let maybe_cumulative_trade = fetch_cumulative_trade(&precisions, &ob_state.last_balances, &liquidity.orderbook)?; - // TODO: delete me - deps.api - .debug(&format!("provide: {:?}", maybe_cumulative_trade)); - let mut pools = liquidity.total_dec(&precisions)?; let old_real_price = config.pool_state.price_state.last_price; @@ -266,7 +262,7 @@ pub fn provide_liquidity( .map(|(asset, deposit)| asset.amount + deposit) .collect_vec(); let cancel_msgs = ob_state.cancel_orders(&env.contract.address); - let order_msgs = ob_state.deploy_orders(&env, &config, &balances, &precisions, deps.api)?; + let order_msgs = ob_state.deploy_orders(&env, &config, &balances, &precisions)?; CONFIG.save(deps.storage, &config)?; @@ -323,10 +319,6 @@ fn withdraw_liquidity( let maybe_cumulative_trade = fetch_cumulative_trade(&precisions, &ob_state.last_balances, &liquidity.orderbook)?; - // TODO: delete me - deps.api - .debug(&format!("withdraw: {:?}", maybe_cumulative_trade)); - let mut pools = liquidity.total_dec(&precisions)?; let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)?; @@ -413,7 +405,7 @@ fn withdraw_liquidity( CONFIG.save(deps.storage, &config)?; - let order_msgs = ob_state.deploy_orders(&env, &config, &xs, &precisions, deps.api)?; + let order_msgs = ob_state.deploy_orders(&env, &config, &xs, &precisions)?; let submsgs = ob_state.flatten_msgs_and_add_callback( &pools_u128, &[cancel_msgs, withdraw_messages], @@ -463,10 +455,6 @@ fn swap( let maybe_cumulative_trade = fetch_cumulative_trade(&precisions, &ob_state.last_balances, &liquidity.orderbook)?; - // TODO: delete me - deps.api - .debug(&format!("swap: {:?}", maybe_cumulative_trade)); - let mut pools = liquidity.total_dec(&precisions)?; let (offer_ind, _) = pools @@ -604,7 +592,7 @@ fn swap( // Reconcile orders let cancel_msgs = ob_state.cancel_orders(&env.contract.address); - let order_msgs = ob_state.deploy_orders(&env, &config, &xs, &precisions, deps.api)?; + let order_msgs = ob_state.deploy_orders(&env, &config, &xs, &precisions)?; CONFIG.save(deps.storage, &config)?; diff --git a/contracts/pair_concentrated_duality/src/orderbook/execute.rs b/contracts/pair_concentrated_duality/src/orderbook/execute.rs index 88a0e5957..41c5986fa 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/execute.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/execute.rs @@ -149,10 +149,6 @@ pub fn sync_pool_with_orderbook( if let Some(cumulative_trade) = fetch_cumulative_trade(&precisions, &ob_state.last_balances, &liquidity.orderbook)? { - deps.api.debug(&format!( - "Syncing pool with orderbook: {:?}", - &cumulative_trade - )); let mut pools = liquidity.total_dec(&precisions)?; let mut balances = pools .iter_mut() @@ -174,7 +170,7 @@ pub fn sync_pool_with_orderbook( let cancel_msgs = ob_state.cancel_orders(&env.contract.address); let balances = pools.iter().map(|asset| asset.amount).collect_vec(); - let order_msgs = ob_state.deploy_orders(&env, &config, &balances, &precisions, deps.api)?; + let order_msgs = ob_state.deploy_orders(&env, &config, &balances, &precisions)?; let pools_u128 = pools .iter() diff --git a/contracts/pair_concentrated_duality/src/orderbook/state.rs b/contracts/pair_concentrated_duality/src/orderbook/state.rs index e413970e7..d4e0bf691 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/state.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/state.rs @@ -315,14 +315,12 @@ impl OrderbookState { /// Construct an array with new orders. /// Return an empty array if orderbook integration is disabled. - // TODO: remove api from the arguments pub fn deploy_orders( &self, env: &Env, config: &Config, balances: &[Decimal256], precisions: &Precisions, - api: &dyn Api, ) -> Result, ContractError> { // Orderbook is disabled. No need to deploy orders. if !self.enabled { @@ -406,7 +404,7 @@ impl OrderbookState { orders_factory.buy(buy_price, buy_amount); } - Ok(orders_factory.collect_spot_orders(&env.contract.address, api)) + Ok(orders_factory.collect_spot_orders(&env.contract.address)) } /// Flatten all messages into one vector and add a callback to the last message only diff --git a/contracts/pair_concentrated_duality/src/orderbook/utils.rs b/contracts/pair_concentrated_duality/src/orderbook/utils.rs index acb53c6ae..4c936d21c 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/utils.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/utils.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use cosmwasm_std::{ - ensure, ensure_eq, Addr, Api, CosmosMsg, Decimal256, OverflowError, QuerierWrapper, StdError, + ensure, ensure_eq, Addr, CosmosMsg, Decimal256, OverflowError, QuerierWrapper, StdError, StdResult, Uint128, Uint256, }; use itertools::Itertools; @@ -127,7 +127,7 @@ impl SpotOrdersFactory { }) } - pub fn collect_spot_orders(self, sender: &Addr, api: &dyn Api) -> Vec { + pub fn collect_spot_orders(self, sender: &Addr) -> Vec { self.orders .into_iter() .map(|order| { @@ -148,13 +148,6 @@ impl SpotOrdersFactory { ) .unwrap(); - api.debug(&format!( - "buy: limit_sell_price: {limit_sell_price} min_average_sell_price: {min_average_sell_price}, amount_in: {}", - (order.amount * self.multiplier[1]) - .to_uint_floor() - .to_string() - )); - #[allow(deprecated)] MsgPlaceLimitOrder { creator: sender.to_string(), @@ -187,13 +180,6 @@ impl SpotOrdersFactory { ) .unwrap(); - api.debug(&format!( - "sell: limit_sell_price: {limit_sell_price} min_average_sell_price: {min_average_sell_price}, amount_in: {}", - (order.amount * self.multiplier[0]) - .to_uint_floor() - .to_string() - )); - #[allow(deprecated)] MsgPlaceLimitOrder { creator: sender.to_string(), diff --git a/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs b/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs index 4a339af60..5bb47d98a 100644 --- a/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs +++ b/contracts/pair_concentrated_duality/tests/pcl_duality_integration.rs @@ -13,9 +13,7 @@ use astroport::pair::{QueryMsg, MAX_FEE_SHARE_BPS}; use astroport::pair_concentrated::{ ConcentratedPoolParams, ConcentratedPoolUpdateParams, PromoteParams, UpdatePoolParams, }; -use astroport::pair_concentrated_duality::{ - DualityPairMsg, MigrateMsg, OrderbookConfig, UpdateDualityOrderbook, -}; +use astroport::pair_concentrated_duality::{DualityPairMsg, MigrateMsg, OrderbookConfig}; use astroport_pair_concentrated_duality::error::ContractError; use astroport_pair_concentrated_duality::instantiate::{CONTRACT_NAME, CONTRACT_VERSION}; use astroport_pair_concentrated_duality::orderbook::error::OrderbookError; From abbdd77ddade9ec94f1aa683d941d74966bd497d Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Tue, 11 Feb 2025 18:39:45 +0300 Subject: [PATCH 19/24] accumulate prices in sync with orderbook endpoint --- .../src/orderbook/execute.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/contracts/pair_concentrated_duality/src/orderbook/execute.rs b/contracts/pair_concentrated_duality/src/orderbook/execute.rs index 41c5986fa..1c87438a4 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/execute.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/execute.rs @@ -10,7 +10,7 @@ use astroport::cosmwasm_ext::{DecimalToInteger, IntegerToDecimal}; use astroport::pair::MIN_TRADE_SIZE; use astroport::querier::{query_fee_info, query_native_supply, FeeInfo}; use astroport_pcl_common::state::{Config, Precisions}; -use astroport_pcl_common::utils::accumulate_prices; +use astroport_pcl_common::utils::{accumulate_prices, calc_last_prices}; use crate::error::ContractError; use crate::instantiate::LP_TOKEN_PRECISION; @@ -95,8 +95,6 @@ pub fn process_cumulative_trade( } } - let old_real_price = config.pool_state.price_state.last_price; - // Skip very small trade sizes which could significantly mess up the price due to rounding errors, // especially if token precisions are 18. if trade.base_asset.amount >= MIN_TRADE_SIZE && trade.quote_asset.amount >= MIN_TRADE_SIZE { @@ -119,13 +117,6 @@ pub fn process_cumulative_trade( .update_price(&config.pool_params, env, total_share, &ixs, last_price)?; } - // TODO: if process_cumulative_trade() originates from swap/provide/withdraw context, - // TODO: next TWAP entry won't be added in this block. - // TODO: Is it ok or should we post TWAP entry - // TODO: formed out of cumulative orderbook trade 1 second before current block time? - // Adding TWAP entry - accumulate_prices(env, config, old_real_price); - Ok(Response::default() .add_messages(messages) .add_attributes(attrs)) @@ -150,6 +141,10 @@ pub fn sync_pool_with_orderbook( fetch_cumulative_trade(&precisions, &ob_state.last_balances, &liquidity.orderbook)? { let mut pools = liquidity.total_dec(&precisions)?; + + let xs = pools.iter().map(|a| a.amount).collect_vec(); + let old_real_price = calc_last_prices(&xs, &config, &env)?; + let mut balances = pools .iter_mut() .map(|asset| &mut asset.amount) @@ -165,6 +160,8 @@ pub fn sync_pool_with_orderbook( None, )?; + accumulate_prices(&env, &mut config, old_real_price); + CONFIG.save(deps.storage, &config)?; let cancel_msgs = ob_state.cancel_orders(&env.contract.address); From fad763c9d7cf46ebc8fdfdf045e5412c98e5eb2d Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Wed, 12 Feb 2025 19:41:43 +0300 Subject: [PATCH 20/24] update deps to astroport forks --- Cargo.lock | 82 +++++++++++++------ contracts/pair_concentrated/Cargo.toml | 2 +- contracts/pair_concentrated/src/contract.rs | 2 +- .../pair_concentrated_duality/Cargo.toml | 8 +- contracts/pair_stable/Cargo.toml | 2 +- packages/astroport/Cargo.toml | 2 +- packages/astroport_pcl_common/Cargo.toml | 2 +- packages/astroport_test/Cargo.toml | 2 +- 8 files changed, 69 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce2851255..2aec50007 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,7 +151,7 @@ dependencies = [ [[package]] name = "astroport" -version = "5.7.0" +version = "5.8.0" dependencies = [ "astroport-circular-buffer 0.2.0", "cosmos-sdk-proto 0.19.0", @@ -212,7 +212,7 @@ name = "astroport-factory" version = "1.9.0" dependencies = [ "anyhow", - "astroport 5.7.0", + "astroport 5.8.0", "astroport-pair 2.1.0", "astroport-test", "cosmwasm-schema", @@ -261,7 +261,7 @@ version = "1.3.0" dependencies = [ "anyhow", "astro-token-converter", - "astroport 5.7.0", + "astroport 5.8.0", "astroport-factory 1.9.0", "astroport-native-coin-registry 1.1.0", "astroport-pair 2.1.0", @@ -372,7 +372,7 @@ dependencies = [ name = "astroport-pair" version = "2.1.0" dependencies = [ - "astroport 5.7.0", + "astroport 5.8.0", "astroport-factory 1.9.0", "astroport-incentives", "astroport-test", @@ -392,10 +392,10 @@ dependencies = [ [[package]] name = "astroport-pair-concentrated" -version = "4.1.0" +version = "4.1.1" dependencies = [ "anyhow", - "astroport 5.7.0", + "astroport 5.8.0", "astroport-circular-buffer 0.2.0", "astroport-factory 1.9.0", "astroport-incentives", @@ -418,10 +418,10 @@ dependencies = [ [[package]] name = "astroport-pair-concentrated-duality" -version = "4.1.0" +version = "4.1.1" dependencies = [ "anyhow", - "astroport 5.7.0", + "astroport 5.8.0", "astroport-factory 1.9.0", "astroport-native-coin-registry 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "astroport-pair-concentrated", @@ -434,7 +434,7 @@ dependencies = [ "cw2 1.1.2", "derivative", "itertools 0.12.1", - "neutron-std", + "neutron-std 5.0.1-rc0 (git+https://github.com/astroport-fi/neutron-std?branch=backport/cosmwasm_v1)", "neutron-test-tube 4.2.2-rc", "semver", "serde", @@ -466,10 +466,10 @@ dependencies = [ [[package]] name = "astroport-pair-stable" -version = "4.1.0" +version = "4.1.1" dependencies = [ "anyhow", - "astroport 5.7.0", + "astroport 5.8.0", "astroport-circular-buffer 0.2.0", "astroport-factory 1.9.0", "astroport-incentives", @@ -495,7 +495,7 @@ name = "astroport-pair-transmuter" version = "1.1.2" dependencies = [ "anyhow", - "astroport 5.7.0", + "astroport 5.8.0", "astroport-factory 1.9.0", "astroport-native-coin-registry 1.1.0", "astroport-test", @@ -516,7 +516,7 @@ name = "astroport-pair-xastro" version = "1.0.0" dependencies = [ "anyhow", - "astroport 5.7.0", + "astroport 5.8.0", "astroport-factory 1.9.0", "astroport-staking", "astroport-test", @@ -534,7 +534,7 @@ dependencies = [ name = "astroport-pair-xyk-sale-tax" version = "2.1.0" dependencies = [ - "astroport 5.7.0", + "astroport 5.8.0", "astroport-factory 1.9.0", "astroport-incentives", "astroport-pair 1.3.3", @@ -557,10 +557,10 @@ dependencies = [ [[package]] name = "astroport-pcl-common" -version = "2.1.0" +version = "2.1.1" dependencies = [ "anyhow", - "astroport 5.7.0", + "astroport 5.8.0", "astroport-factory 1.9.0", "astroport-test", "cosmwasm-schema", @@ -596,7 +596,7 @@ version = "2.3.0" dependencies = [ "anyhow", "astroport 4.0.3", - "astroport 5.7.0", + "astroport 5.8.0", "astroport-tokenfactory-tracker 1.0.0", "cosmwasm-schema", "cosmwasm-std", @@ -614,7 +614,7 @@ name = "astroport-test" version = "0.1.0" dependencies = [ "anyhow", - "astroport 5.7.0", + "astroport 5.8.0", "astroport-factory 1.9.0", "cosmwasm-schema", "cosmwasm-std", @@ -623,7 +623,7 @@ dependencies = [ "cw-utils 1.0.3", "cw20-base 1.1.2", "itertools 0.12.1", - "neutron-std", + "neutron-std 5.0.1-rc0 (git+https://github.com/astroport-fi/neutron-std?branch=backport/cosmwasm_v1)", "osmosis-std", "prost 0.12.6", "schemars", @@ -2364,6 +2364,30 @@ dependencies = [ "thiserror", ] +[[package]] +name = "neutron-std" +version = "5.0.1-rc0" +source = "git+https://github.com/astroport-fi/neutron-std?branch=backport/cosmwasm_v1#6079bcb81d6f643b66124997002ac25b471fa24a" +dependencies = [ + "bech32 0.9.1", + "chrono", + "cosmos-sdk-proto 0.20.0", + "cosmwasm-schema", + "cosmwasm-std", + "neutron-std-derive 0.20.1 (git+https://github.com/astroport-fi/neutron-std?branch=backport/cosmwasm_v1)", + "prost 0.12.6", + "prost-types 0.12.6", + "protobuf 3.3.0", + "schemars", + "serde", + "serde-cw-value", + "serde-json-wasm 1.0.1", + "serde_json", + "speedate", + "tendermint-proto 0.34.1", + "thiserror", +] + [[package]] name = "neutron-std" version = "5.0.1-rc0" @@ -2374,7 +2398,7 @@ dependencies = [ "cosmos-sdk-proto 0.20.0", "cosmwasm-schema", "cosmwasm-std", - "neutron-std-derive", + "neutron-std-derive 0.20.1 (git+https://github.com/epanchee/neutron-std?branch=backport/cosmwasm_v1)", "prost 0.12.6", "prost-types 0.12.6", "protobuf 3.3.0", @@ -2388,6 +2412,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "neutron-std-derive" +version = "0.20.1" +source = "git+https://github.com/astroport-fi/neutron-std?branch=backport/cosmwasm_v1#6079bcb81d6f643b66124997002ac25b471fa24a" +dependencies = [ + "itertools 0.10.5", + "proc-macro2", + "prost-types 0.12.6", + "quote", + "syn 1.0.109", +] + [[package]] name = "neutron-std-derive" version = "0.20.1" @@ -2420,7 +2456,7 @@ dependencies = [ [[package]] name = "neutron-test-tube" version = "4.2.2-rc" -source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/neutron-v5-cosmwasm-v1#98b8284b7920bcf1061da85cdc6fc7fd0f0c623f" +source = "git+https://github.com/astroport-fi/neutron-test-tube?branch=feat/neutron-v5-cosmwasm-v1#98b8284b7920bcf1061da85cdc6fc7fd0f0c623f" dependencies = [ "base64", "bindgen 0.60.1", @@ -2428,7 +2464,7 @@ dependencies = [ "cosmrs", "cosmwasm-std", "hex", - "neutron-std", + "neutron-std 5.0.1-rc0 (git+https://github.com/epanchee/neutron-std?branch=backport/cosmwasm_v1)", "prost 0.12.6", "serde", "serde_json", @@ -3790,7 +3826,7 @@ dependencies = [ [[package]] name = "test-tube-ntrn" version = "0.1.0" -source = "git+https://github.com/epanchee/neutron-test-tube-new?branch=feat/neutron-v5-cosmwasm-v1#98b8284b7920bcf1061da85cdc6fc7fd0f0c623f" +source = "git+https://github.com/astroport-fi/neutron-test-tube?branch=feat/neutron-v5-cosmwasm-v1#98b8284b7920bcf1061da85cdc6fc7fd0f0c623f" dependencies = [ "base64", "cosmos-sdk-proto 0.20.0", diff --git a/contracts/pair_concentrated/Cargo.toml b/contracts/pair_concentrated/Cargo.toml index 51cb1e06a..d21900693 100644 --- a/contracts/pair_concentrated/Cargo.toml +++ b/contracts/pair_concentrated/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-pair-concentrated" -version = "4.1.0" +version = "4.1.1" authors = ["Astroport"] edition = "2021" description = "The Astroport concentrated liquidity pair" diff --git a/contracts/pair_concentrated/src/contract.rs b/contracts/pair_concentrated/src/contract.rs index eb3a030b6..3d5dfb60d 100644 --- a/contracts/pair_concentrated/src/contract.rs +++ b/contracts/pair_concentrated/src/contract.rs @@ -906,7 +906,7 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result match contract_version.version.as_ref() { - "4.0.0" | "4.0.1" => {} + "4.0.0" | "4.0.1" | "4.1.0" => {} _ => return Err(ContractError::MigrationError {}), }, _ => return Err(ContractError::MigrationError {}), diff --git a/contracts/pair_concentrated_duality/Cargo.toml b/contracts/pair_concentrated_duality/Cargo.toml index 661899ccf..b691a1b6b 100644 --- a/contracts/pair_concentrated_duality/Cargo.toml +++ b/contracts/pair_concentrated_duality/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-pair-concentrated-duality" -version = "4.1.0" # must share the same version with the original PCL +version = "4.1.1" # must share the same version with the original PCL authors = ["Astroport"] edition = "2021" description = "The Astroport concentrated liquidity pair with Duality orderbook integration" @@ -35,14 +35,14 @@ itertools = { workspace = true } cw-utils = { workspace = true } cw2 = { workspace = true } -astroport = { path = "../../packages/astroport", version = "5.7", features = ["duality"] } +astroport = { path = "../../packages/astroport", version = "5.8", features = ["duality"] } astroport-pcl-common = { path = "../../packages/astroport_pcl_common", version = "2" } serde = { version = "1.0.203", features = ["derive"] } semver = "1.0" -neutron-std = { git = "https://github.com/epanchee/neutron-std", branch = "backport/cosmwasm_v1" } -neutron-test-tube = { git = "https://github.com/epanchee/neutron-test-tube-new", optional = true, branch = "feat/neutron-v5-cosmwasm-v1", version = "4.2.2-rc" } +neutron-std = { git = "https://github.com/astroport-fi/neutron-std", branch = "backport/cosmwasm_v1" } +neutron-test-tube = { git = "https://github.com/astroport-fi/neutron-test-tube", optional = true, branch = "feat/neutron-v5-cosmwasm-v1", version = "4.2.2-rc" } [dev-dependencies] astroport-native-coin-registry = { version = "1.1", features = ["library"] } diff --git a/contracts/pair_stable/Cargo.toml b/contracts/pair_stable/Cargo.toml index 8ea73c897..99fd64482 100644 --- a/contracts/pair_stable/Cargo.toml +++ b/contracts/pair_stable/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-pair-stable" -version = "4.1.0" +version = "4.1.1" authors = ["Astroport"] edition = "2021" description = "The Astroport stableswap pair contract implementation" diff --git a/packages/astroport/Cargo.toml b/packages/astroport/Cargo.toml index 9b3528939..3082cc2b4 100644 --- a/packages/astroport/Cargo.toml +++ b/packages/astroport/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport" -version = "5.7.0" +version = "5.8.0" authors = ["Astroport"] edition = "2021" description = "Common Astroport types, queriers and other utils" diff --git a/packages/astroport_pcl_common/Cargo.toml b/packages/astroport_pcl_common/Cargo.toml index 64193bfb4..1fed89a4f 100644 --- a/packages/astroport_pcl_common/Cargo.toml +++ b/packages/astroport_pcl_common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-pcl-common" -version = "2.1.0" +version = "2.1.1" edition = "2021" description = "Common package contains math tools and utils for Astroport PCL pairs" license = "GPL-3.0-only" diff --git a/packages/astroport_test/Cargo.toml b/packages/astroport_test/Cargo.toml index 644232504..c0fad4c00 100644 --- a/packages/astroport_test/Cargo.toml +++ b/packages/astroport_test/Cargo.toml @@ -33,6 +33,6 @@ itertools = { workspace = true } cw-utils = { workspace = true } cw-storage-plus = { workspace = true } osmosis-std = "0.21.0" -neutron-std = { git = "https://github.com/epanchee/neutron-std", branch = "backport/cosmwasm_v1" } +neutron-std = { git = "https://github.com/astroport-fi/neutron-std", branch = "backport/cosmwasm_v1" } prost = "0.12" sha2 = "0.10.8" \ No newline at end of file From e4ce036a34be478a775dcb2e6f97bbf309600dd1 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Wed, 12 Feb 2025 19:45:03 +0300 Subject: [PATCH 21/24] update schemas --- .../astroport-pair-concentrated-duality.json | 1796 +++++++++++++++++ .../raw/execute.json | 475 +++++ .../raw/instantiate.json | 156 ++ .../raw/query.json | 364 ++++ .../raw/response_to_asset_balance_at.json | 18 + .../raw/response_to_config.json | 68 + .../raw/response_to_cumulative_prices.json | 135 ++ .../raw/response_to_observe.json | 26 + .../raw/response_to_pair.json | 143 ++ .../raw/response_to_pool.json | 114 ++ .../raw/response_to_query_compute_d.json | 6 + .../raw/response_to_reverse_simulation.json | 44 + .../raw/response_to_share.json | 94 + .../raw/response_to_simulate_provide.json | 6 + .../raw/response_to_simulate_withdraw.json | 94 + .../raw/response_to_simulation.json | 44 + .../astroport-pair-concentrated.json | 19 +- .../raw/execute.json | 17 + .../astroport-pair-stable.json | 19 +- .../astroport-pair-stable/raw/execute.json | 17 + .../astroport-pair-xastro.json | 17 + .../astroport-pair-xastro/raw/execute.json | 17 + .../astroport-pair-xyk-sale-tax.json | 17 + .../raw/execute.json | 17 + schemas/astroport-pair/astroport-pair.json | 17 + schemas/astroport-pair/raw/execute.json | 17 + 26 files changed, 3755 insertions(+), 2 deletions(-) create mode 100644 schemas/astroport-pair-concentrated-duality/astroport-pair-concentrated-duality.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/execute.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/instantiate.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/query.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/response_to_asset_balance_at.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/response_to_config.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/response_to_cumulative_prices.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/response_to_observe.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/response_to_pair.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/response_to_pool.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/response_to_query_compute_d.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/response_to_reverse_simulation.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/response_to_share.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/response_to_simulate_provide.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/response_to_simulate_withdraw.json create mode 100644 schemas/astroport-pair-concentrated-duality/raw/response_to_simulation.json diff --git a/schemas/astroport-pair-concentrated-duality/astroport-pair-concentrated-duality.json b/schemas/astroport-pair-concentrated-duality/astroport-pair-concentrated-duality.json new file mode 100644 index 000000000..025808e70 --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/astroport-pair-concentrated-duality.json @@ -0,0 +1,1796 @@ +{ + "contract_name": "astroport-pair-concentrated-duality", + "contract_version": "4.1.1", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "This structure describes the parameters used for creating a contract.", + "type": "object", + "required": [ + "asset_infos", + "factory_addr", + "pair_type", + "token_code_id" + ], + "properties": { + "asset_infos": { + "description": "Information about assets in the pool", + "type": "array", + "items": { + "$ref": "#/definitions/AssetInfo" + } + }, + "factory_addr": { + "description": "The factory contract address", + "type": "string" + }, + "init_params": { + "description": "Optional binary serialised parameters for custom pool types", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "pair_type": { + "description": "The pair type", + "allOf": [ + { + "$ref": "#/definitions/PairType" + } + ] + }, + "token_code_id": { + "description": "The token contract code ID used for the tokens in the pool", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "PairType": { + "description": "This enum describes available pair types. ## Available pool types ``` # use astroport::factory::PairType::{Custom, Stable, Xyk}; Xyk {}; Stable {}; Custom(String::from(\"Custom\")); ```", + "oneOf": [ + { + "description": "XYK pair type", + "type": "object", + "required": [ + "xyk" + ], + "properties": { + "xyk": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Stable pair type", + "type": "object", + "required": [ + "stable" + ], + "properties": { + "stable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Custom pair type", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This structure describes the execute messages available in the contract.", + "oneOf": [ + { + "description": "Receives a message of type [`Cw20ReceiveMsg`]", + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "additionalProperties": false + }, + { + "description": "ProvideLiquidity allows someone to provide liquidity in the pool", + "type": "object", + "required": [ + "provide_liquidity" + ], + "properties": { + "provide_liquidity": { + "type": "object", + "required": [ + "assets" + ], + "properties": { + "assets": { + "description": "The assets available in the pool", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "auto_stake": { + "description": "Determines whether the LP tokens minted for the user is auto_staked in the Incentives contract", + "type": [ + "boolean", + "null" + ] + }, + "min_lp_to_receive": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "receiver": { + "description": "The receiver of LP tokens", + "type": [ + "string", + "null" + ] + }, + "slippage_tolerance": { + "description": "The slippage tolerance that allows liquidity provision only if the price in the pool doesn't move too much", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "WithdrawLiquidity allows someone to withdraw liquidity from the pool", + "type": "object", + "required": [ + "withdraw_liquidity" + ], + "properties": { + "withdraw_liquidity": { + "type": "object", + "properties": { + "assets": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "min_assets_to_receive": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Asset" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Swap performs a swap in the pool", + "type": "object", + "required": [ + "swap" + ], + "properties": { + "swap": { + "type": "object", + "required": [ + "offer_asset" + ], + "properties": { + "ask_asset_info": { + "anyOf": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "type": "null" + } + ] + }, + "belief_price": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "max_spread": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "offer_asset": { + "$ref": "#/definitions/Asset" + }, + "to": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update the pair configuration", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "params" + ], + "properties": { + "params": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ProposeNewOwner creates a proposal to change contract ownership. The validity period for the proposal is set in the `expires_in` variable.", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "owner" + ], + "properties": { + "expires_in": { + "description": "The date after which this proposal expires", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "Newly proposed contract owner", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "DropOwnershipProposal removes the existing offer to change contract ownership.", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Used to claim contract ownership.", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Custom execute endpoints for extended pool implementations", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/DualityPairMsg" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "DualityPairMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "sync_orderbook" + ], + "properties": { + "sync_orderbook": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_orderbook_config" + ], + "properties": { + "update_orderbook_config": { + "$ref": "#/definitions/UpdateDualityOrderbook" + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "UpdateDualityOrderbook": { + "type": "object", + "properties": { + "enable": { + "description": "Determines whether the orderbook is enabled", + "type": [ + "boolean", + "null" + ] + }, + "executor": { + "description": "The address of the orderbook sync executor", + "type": [ + "string", + "null" + ] + }, + "liquidity_percent": { + "description": "Percent of liquidity to be deployed to the orderbook", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "min_asset_0_order_size": { + "description": "Minimum order size for asset 0", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "min_asset_1_order_size": { + "description": "Minimum order size for asset 1", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "orders_number": { + "description": "Number of orders on each side of the orderbook", + "type": [ + "integer", + "null" + ], + "format": "uint8", + "minimum": 0.0 + }, + "remove_executor": { + "description": "Determines whether the executor should be removed. If removed, then sync endpoint becomes permissionless", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "This structure describes the query messages available in the contract.", + "oneOf": [ + { + "description": "Returns information about a pair in an object of type [`super::asset::PairInfo`].", + "type": "object", + "required": [ + "pair" + ], + "properties": { + "pair": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about a pool in an object of type [`PoolResponse`].", + "type": "object", + "required": [ + "pool" + ], + "properties": { + "pool": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns contract configuration settings in a custom [`ConfigResponse`] structure.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about the share of the pool in a vector that contains objects of type [`Asset`].", + "type": "object", + "required": [ + "share" + ], + "properties": { + "share": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about a swap simulation in a [`SimulationResponse`] object.", + "type": "object", + "required": [ + "simulation" + ], + "properties": { + "simulation": { + "type": "object", + "required": [ + "offer_asset" + ], + "properties": { + "ask_asset_info": { + "anyOf": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "type": "null" + } + ] + }, + "offer_asset": { + "$ref": "#/definitions/Asset" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about cumulative prices in a [`ReverseSimulationResponse`] object.", + "type": "object", + "required": [ + "reverse_simulation" + ], + "properties": { + "reverse_simulation": { + "type": "object", + "required": [ + "ask_asset" + ], + "properties": { + "ask_asset": { + "$ref": "#/definitions/Asset" + }, + "offer_asset_info": { + "anyOf": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about the cumulative prices in a [`CumulativePricesResponse`] object", + "type": "object", + "required": [ + "cumulative_prices" + ], + "properties": { + "cumulative_prices": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns current D invariant in as a [`u128`] value", + "type": "object", + "required": [ + "query_compute_d" + ], + "properties": { + "query_compute_d": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the balance of the specified asset that was in the pool just preceeding the moment of the specified block height creation.", + "type": "object", + "required": [ + "asset_balance_at" + ], + "properties": { + "asset_balance_at": { + "type": "object", + "required": [ + "asset_info", + "block_height" + ], + "properties": { + "asset_info": { + "$ref": "#/definitions/AssetInfo" + }, + "block_height": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Query price from observations", + "type": "object", + "required": [ + "observe" + ], + "properties": { + "observe": { + "type": "object", + "required": [ + "seconds_ago" + ], + "properties": { + "seconds_ago": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns an estimation of assets received for the given amount of LP tokens", + "type": "object", + "required": [ + "simulate_withdraw" + ], + "properties": { + "simulate_withdraw": { + "type": "object", + "required": [ + "lp_amount" + ], + "properties": { + "lp_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns an estimation of shares received for the given amount of assets", + "type": "object", + "required": [ + "simulate_provide" + ], + "properties": { + "simulate_provide": { + "type": "object", + "required": [ + "assets" + ], + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "migrate": null, + "sudo": null, + "responses": { + "asset_balance_at": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_Uint128", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ], + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "description": "This struct is used to return a query result with the general contract configuration.", + "type": "object", + "required": [ + "block_time_last", + "factory_addr", + "owner" + ], + "properties": { + "block_time_last": { + "description": "Last timestamp when the cumulative prices in the pool were updated", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "factory_addr": { + "description": "The factory contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "owner": { + "description": "The contract owner", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "params": { + "description": "The pool's parameters", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "tracker_addr": { + "description": "Tracker contract address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + } + } + }, + "cumulative_prices": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CumulativePricesResponse", + "description": "This structure is used to return a cumulative prices query response.", + "type": "object", + "required": [ + "assets", + "cumulative_prices", + "total_share" + ], + "properties": { + "assets": { + "description": "The assets in the pool to query", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "cumulative_prices": { + "description": "The vector contains cumulative prices for each pair of assets in the pool", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "$ref": "#/definitions/AssetInfo" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 3, + "minItems": 3 + } + }, + "total_share": { + "description": "The total amount of LP tokens currently issued", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "observe": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OracleObservation", + "type": "object", + "required": [ + "price", + "timestamp" + ], + "properties": { + "price": { + "$ref": "#/definitions/Decimal" + }, + "timestamp": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, + "pair": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PairInfo", + "description": "This structure stores the main parameters for an Astroport pair", + "type": "object", + "required": [ + "asset_infos", + "contract_addr", + "liquidity_token", + "pair_type" + ], + "properties": { + "asset_infos": { + "description": "Asset information for the assets in the pool", + "type": "array", + "items": { + "$ref": "#/definitions/AssetInfo" + } + }, + "contract_addr": { + "description": "Pair contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "liquidity_token": { + "description": "Pair LP token denom", + "type": "string" + }, + "pair_type": { + "description": "The pool type (xyk, stableswap etc) available in [`PairType`]", + "allOf": [ + { + "$ref": "#/definitions/PairType" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "PairType": { + "description": "This enum describes available pair types. ## Available pool types ``` # use astroport::factory::PairType::{Custom, Stable, Xyk}; Xyk {}; Stable {}; Custom(String::from(\"Custom\")); ```", + "oneOf": [ + { + "description": "XYK pair type", + "type": "object", + "required": [ + "xyk" + ], + "properties": { + "xyk": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Stable pair type", + "type": "object", + "required": [ + "stable" + ], + "properties": { + "stable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Custom pair type", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + } + } + }, + "pool": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PoolResponse", + "description": "This struct is used to return a query result with the total amount of LP tokens and assets in a specific pool.", + "type": "object", + "required": [ + "assets", + "total_share" + ], + "properties": { + "assets": { + "description": "The assets in the pool together with asset amounts", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "total_share": { + "description": "The total amount of LP tokens currently issued", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query_compute_d": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "reverse_simulation": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ReverseSimulationResponse", + "description": "This structure holds the parameters that are returned from a reverse swap simulation response.", + "type": "object", + "required": [ + "commission_amount", + "offer_amount", + "spread_amount" + ], + "properties": { + "commission_amount": { + "description": "The amount of fees charged by the transaction", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "offer_amount": { + "description": "The amount of offer assets returned by the reverse swap", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "spread_amount": { + "description": "The spread used in the swap operation", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "share": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Asset", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "simulate_provide": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "simulate_withdraw": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Asset", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "simulation": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SimulationResponse", + "description": "This structure holds the parameters that are returned from a swap simulation response", + "type": "object", + "required": [ + "commission_amount", + "return_amount", + "spread_amount" + ], + "properties": { + "commission_amount": { + "description": "The amount of fees charged by the transaction", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "return_amount": { + "description": "The amount of ask assets returned by the swap", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "spread_amount": { + "description": "The spread used in the swap operation", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + } + } +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/execute.json b/schemas/astroport-pair-concentrated-duality/raw/execute.json new file mode 100644 index 000000000..554fb60c7 --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/execute.json @@ -0,0 +1,475 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This structure describes the execute messages available in the contract.", + "oneOf": [ + { + "description": "Receives a message of type [`Cw20ReceiveMsg`]", + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "additionalProperties": false + }, + { + "description": "ProvideLiquidity allows someone to provide liquidity in the pool", + "type": "object", + "required": [ + "provide_liquidity" + ], + "properties": { + "provide_liquidity": { + "type": "object", + "required": [ + "assets" + ], + "properties": { + "assets": { + "description": "The assets available in the pool", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "auto_stake": { + "description": "Determines whether the LP tokens minted for the user is auto_staked in the Incentives contract", + "type": [ + "boolean", + "null" + ] + }, + "min_lp_to_receive": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "receiver": { + "description": "The receiver of LP tokens", + "type": [ + "string", + "null" + ] + }, + "slippage_tolerance": { + "description": "The slippage tolerance that allows liquidity provision only if the price in the pool doesn't move too much", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "WithdrawLiquidity allows someone to withdraw liquidity from the pool", + "type": "object", + "required": [ + "withdraw_liquidity" + ], + "properties": { + "withdraw_liquidity": { + "type": "object", + "properties": { + "assets": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "min_assets_to_receive": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Asset" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Swap performs a swap in the pool", + "type": "object", + "required": [ + "swap" + ], + "properties": { + "swap": { + "type": "object", + "required": [ + "offer_asset" + ], + "properties": { + "ask_asset_info": { + "anyOf": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "type": "null" + } + ] + }, + "belief_price": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "max_spread": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "offer_asset": { + "$ref": "#/definitions/Asset" + }, + "to": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update the pair configuration", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "params" + ], + "properties": { + "params": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ProposeNewOwner creates a proposal to change contract ownership. The validity period for the proposal is set in the `expires_in` variable.", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "owner" + ], + "properties": { + "expires_in": { + "description": "The date after which this proposal expires", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "Newly proposed contract owner", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "DropOwnershipProposal removes the existing offer to change contract ownership.", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Used to claim contract ownership.", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Custom execute endpoints for extended pool implementations", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/DualityPairMsg" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "DualityPairMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "sync_orderbook" + ], + "properties": { + "sync_orderbook": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_orderbook_config" + ], + "properties": { + "update_orderbook_config": { + "$ref": "#/definitions/UpdateDualityOrderbook" + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "UpdateDualityOrderbook": { + "type": "object", + "properties": { + "enable": { + "description": "Determines whether the orderbook is enabled", + "type": [ + "boolean", + "null" + ] + }, + "executor": { + "description": "The address of the orderbook sync executor", + "type": [ + "string", + "null" + ] + }, + "liquidity_percent": { + "description": "Percent of liquidity to be deployed to the orderbook", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "min_asset_0_order_size": { + "description": "Minimum order size for asset 0", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "min_asset_1_order_size": { + "description": "Minimum order size for asset 1", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "orders_number": { + "description": "Number of orders on each side of the orderbook", + "type": [ + "integer", + "null" + ], + "format": "uint8", + "minimum": 0.0 + }, + "remove_executor": { + "description": "Determines whether the executor should be removed. If removed, then sync endpoint becomes permissionless", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + } + } +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/instantiate.json b/schemas/astroport-pair-concentrated-duality/raw/instantiate.json new file mode 100644 index 000000000..00ccada94 --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/instantiate.json @@ -0,0 +1,156 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "This structure describes the parameters used for creating a contract.", + "type": "object", + "required": [ + "asset_infos", + "factory_addr", + "pair_type", + "token_code_id" + ], + "properties": { + "asset_infos": { + "description": "Information about assets in the pool", + "type": "array", + "items": { + "$ref": "#/definitions/AssetInfo" + } + }, + "factory_addr": { + "description": "The factory contract address", + "type": "string" + }, + "init_params": { + "description": "Optional binary serialised parameters for custom pool types", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "pair_type": { + "description": "The pair type", + "allOf": [ + { + "$ref": "#/definitions/PairType" + } + ] + }, + "token_code_id": { + "description": "The token contract code ID used for the tokens in the pool", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "PairType": { + "description": "This enum describes available pair types. ## Available pool types ``` # use astroport::factory::PairType::{Custom, Stable, Xyk}; Xyk {}; Stable {}; Custom(String::from(\"Custom\")); ```", + "oneOf": [ + { + "description": "XYK pair type", + "type": "object", + "required": [ + "xyk" + ], + "properties": { + "xyk": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Stable pair type", + "type": "object", + "required": [ + "stable" + ], + "properties": { + "stable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Custom pair type", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/query.json b/schemas/astroport-pair-concentrated-duality/raw/query.json new file mode 100644 index 000000000..b7da792ec --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/query.json @@ -0,0 +1,364 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "This structure describes the query messages available in the contract.", + "oneOf": [ + { + "description": "Returns information about a pair in an object of type [`super::asset::PairInfo`].", + "type": "object", + "required": [ + "pair" + ], + "properties": { + "pair": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about a pool in an object of type [`PoolResponse`].", + "type": "object", + "required": [ + "pool" + ], + "properties": { + "pool": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns contract configuration settings in a custom [`ConfigResponse`] structure.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about the share of the pool in a vector that contains objects of type [`Asset`].", + "type": "object", + "required": [ + "share" + ], + "properties": { + "share": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about a swap simulation in a [`SimulationResponse`] object.", + "type": "object", + "required": [ + "simulation" + ], + "properties": { + "simulation": { + "type": "object", + "required": [ + "offer_asset" + ], + "properties": { + "ask_asset_info": { + "anyOf": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "type": "null" + } + ] + }, + "offer_asset": { + "$ref": "#/definitions/Asset" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about cumulative prices in a [`ReverseSimulationResponse`] object.", + "type": "object", + "required": [ + "reverse_simulation" + ], + "properties": { + "reverse_simulation": { + "type": "object", + "required": [ + "ask_asset" + ], + "properties": { + "ask_asset": { + "$ref": "#/definitions/Asset" + }, + "offer_asset_info": { + "anyOf": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about the cumulative prices in a [`CumulativePricesResponse`] object", + "type": "object", + "required": [ + "cumulative_prices" + ], + "properties": { + "cumulative_prices": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns current D invariant in as a [`u128`] value", + "type": "object", + "required": [ + "query_compute_d" + ], + "properties": { + "query_compute_d": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the balance of the specified asset that was in the pool just preceeding the moment of the specified block height creation.", + "type": "object", + "required": [ + "asset_balance_at" + ], + "properties": { + "asset_balance_at": { + "type": "object", + "required": [ + "asset_info", + "block_height" + ], + "properties": { + "asset_info": { + "$ref": "#/definitions/AssetInfo" + }, + "block_height": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Query price from observations", + "type": "object", + "required": [ + "observe" + ], + "properties": { + "observe": { + "type": "object", + "required": [ + "seconds_ago" + ], + "properties": { + "seconds_ago": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns an estimation of assets received for the given amount of LP tokens", + "type": "object", + "required": [ + "simulate_withdraw" + ], + "properties": { + "simulate_withdraw": { + "type": "object", + "required": [ + "lp_amount" + ], + "properties": { + "lp_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns an estimation of shares received for the given amount of assets", + "type": "object", + "required": [ + "simulate_provide" + ], + "properties": { + "simulate_provide": { + "type": "object", + "required": [ + "assets" + ], + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/response_to_asset_balance_at.json b/schemas/astroport-pair-concentrated-duality/raw/response_to_asset_balance_at.json new file mode 100644 index 000000000..2eaf6e96f --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/response_to_asset_balance_at.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_Uint128", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ], + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/response_to_config.json b/schemas/astroport-pair-concentrated-duality/raw/response_to_config.json new file mode 100644 index 000000000..953b0adc7 --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/response_to_config.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "description": "This struct is used to return a query result with the general contract configuration.", + "type": "object", + "required": [ + "block_time_last", + "factory_addr", + "owner" + ], + "properties": { + "block_time_last": { + "description": "Last timestamp when the cumulative prices in the pool were updated", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "factory_addr": { + "description": "The factory contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "owner": { + "description": "The contract owner", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "params": { + "description": "The pool's parameters", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "tracker_addr": { + "description": "Tracker contract address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + } + } +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/response_to_cumulative_prices.json b/schemas/astroport-pair-concentrated-duality/raw/response_to_cumulative_prices.json new file mode 100644 index 000000000..695a3121f --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/response_to_cumulative_prices.json @@ -0,0 +1,135 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CumulativePricesResponse", + "description": "This structure is used to return a cumulative prices query response.", + "type": "object", + "required": [ + "assets", + "cumulative_prices", + "total_share" + ], + "properties": { + "assets": { + "description": "The assets in the pool to query", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "cumulative_prices": { + "description": "The vector contains cumulative prices for each pair of assets in the pool", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "$ref": "#/definitions/AssetInfo" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 3, + "minItems": 3 + } + }, + "total_share": { + "description": "The total amount of LP tokens currently issued", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/response_to_observe.json b/schemas/astroport-pair-concentrated-duality/raw/response_to_observe.json new file mode 100644 index 000000000..a8c389cb0 --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/response_to_observe.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OracleObservation", + "type": "object", + "required": [ + "price", + "timestamp" + ], + "properties": { + "price": { + "$ref": "#/definitions/Decimal" + }, + "timestamp": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/response_to_pair.json b/schemas/astroport-pair-concentrated-duality/raw/response_to_pair.json new file mode 100644 index 000000000..16721f81d --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/response_to_pair.json @@ -0,0 +1,143 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PairInfo", + "description": "This structure stores the main parameters for an Astroport pair", + "type": "object", + "required": [ + "asset_infos", + "contract_addr", + "liquidity_token", + "pair_type" + ], + "properties": { + "asset_infos": { + "description": "Asset information for the assets in the pool", + "type": "array", + "items": { + "$ref": "#/definitions/AssetInfo" + } + }, + "contract_addr": { + "description": "Pair contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "liquidity_token": { + "description": "Pair LP token denom", + "type": "string" + }, + "pair_type": { + "description": "The pool type (xyk, stableswap etc) available in [`PairType`]", + "allOf": [ + { + "$ref": "#/definitions/PairType" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "PairType": { + "description": "This enum describes available pair types. ## Available pool types ``` # use astroport::factory::PairType::{Custom, Stable, Xyk}; Xyk {}; Stable {}; Custom(String::from(\"Custom\")); ```", + "oneOf": [ + { + "description": "XYK pair type", + "type": "object", + "required": [ + "xyk" + ], + "properties": { + "xyk": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Stable pair type", + "type": "object", + "required": [ + "stable" + ], + "properties": { + "stable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Custom pair type", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/response_to_pool.json b/schemas/astroport-pair-concentrated-duality/raw/response_to_pool.json new file mode 100644 index 000000000..693cad0e0 --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/response_to_pool.json @@ -0,0 +1,114 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PoolResponse", + "description": "This struct is used to return a query result with the total amount of LP tokens and assets in a specific pool.", + "type": "object", + "required": [ + "assets", + "total_share" + ], + "properties": { + "assets": { + "description": "The assets in the pool together with asset amounts", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "total_share": { + "description": "The total amount of LP tokens currently issued", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/response_to_query_compute_d.json b/schemas/astroport-pair-concentrated-duality/raw/response_to_query_compute_d.json new file mode 100644 index 000000000..25b73e8f2 --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/response_to_query_compute_d.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/response_to_reverse_simulation.json b/schemas/astroport-pair-concentrated-duality/raw/response_to_reverse_simulation.json new file mode 100644 index 000000000..ca711e182 --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/response_to_reverse_simulation.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ReverseSimulationResponse", + "description": "This structure holds the parameters that are returned from a reverse swap simulation response.", + "type": "object", + "required": [ + "commission_amount", + "offer_amount", + "spread_amount" + ], + "properties": { + "commission_amount": { + "description": "The amount of fees charged by the transaction", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "offer_amount": { + "description": "The amount of offer assets returned by the reverse swap", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "spread_amount": { + "description": "The spread used in the swap operation", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/response_to_share.json b/schemas/astroport-pair-concentrated-duality/raw/response_to_share.json new file mode 100644 index 000000000..8285d4916 --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/response_to_share.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Asset", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/response_to_simulate_provide.json b/schemas/astroport-pair-concentrated-duality/raw/response_to_simulate_provide.json new file mode 100644 index 000000000..25b73e8f2 --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/response_to_simulate_provide.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/response_to_simulate_withdraw.json b/schemas/astroport-pair-concentrated-duality/raw/response_to_simulate_withdraw.json new file mode 100644 index 000000000..8285d4916 --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/response_to_simulate_withdraw.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Asset", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-pair-concentrated-duality/raw/response_to_simulation.json b/schemas/astroport-pair-concentrated-duality/raw/response_to_simulation.json new file mode 100644 index 000000000..4bc828d4a --- /dev/null +++ b/schemas/astroport-pair-concentrated-duality/raw/response_to_simulation.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SimulationResponse", + "description": "This structure holds the parameters that are returned from a swap simulation response", + "type": "object", + "required": [ + "commission_amount", + "return_amount", + "spread_amount" + ], + "properties": { + "commission_amount": { + "description": "The amount of fees charged by the transaction", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "return_amount": { + "description": "The amount of ask assets returned by the swap", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "spread_amount": { + "description": "The spread used in the swap operation", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-pair-concentrated/astroport-pair-concentrated.json b/schemas/astroport-pair-concentrated/astroport-pair-concentrated.json index e1597e489..08f5312ca 100644 --- a/schemas/astroport-pair-concentrated/astroport-pair-concentrated.json +++ b/schemas/astroport-pair-concentrated/astroport-pair-concentrated.json @@ -1,6 +1,6 @@ { "contract_name": "astroport-pair-concentrated", - "contract_version": "4.1.0", + "contract_version": "4.1.1", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -406,6 +406,19 @@ } }, "additionalProperties": false + }, + { + "description": "Custom execute endpoints for extended pool implementations", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false } ], "definitions": { @@ -518,6 +531,10 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/astroport-pair-concentrated/raw/execute.json b/schemas/astroport-pair-concentrated/raw/execute.json index 443466010..3a6ac6f12 100644 --- a/schemas/astroport-pair-concentrated/raw/execute.json +++ b/schemas/astroport-pair-concentrated/raw/execute.json @@ -246,6 +246,19 @@ } }, "additionalProperties": false + }, + { + "description": "Custom execute endpoints for extended pool implementations", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false } ], "definitions": { @@ -358,6 +371,10 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/astroport-pair-stable/astroport-pair-stable.json b/schemas/astroport-pair-stable/astroport-pair-stable.json index dafacb443..c7cf51c27 100644 --- a/schemas/astroport-pair-stable/astroport-pair-stable.json +++ b/schemas/astroport-pair-stable/astroport-pair-stable.json @@ -1,6 +1,6 @@ { "contract_name": "astroport-pair-stable", - "contract_version": "4.1.0", + "contract_version": "4.1.1", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -406,6 +406,19 @@ } }, "additionalProperties": false + }, + { + "description": "Custom execute endpoints for extended pool implementations", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false } ], "definitions": { @@ -518,6 +531,10 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/astroport-pair-stable/raw/execute.json b/schemas/astroport-pair-stable/raw/execute.json index 443466010..3a6ac6f12 100644 --- a/schemas/astroport-pair-stable/raw/execute.json +++ b/schemas/astroport-pair-stable/raw/execute.json @@ -246,6 +246,19 @@ } }, "additionalProperties": false + }, + { + "description": "Custom execute endpoints for extended pool implementations", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false } ], "definitions": { @@ -358,6 +371,10 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/astroport-pair-xastro/astroport-pair-xastro.json b/schemas/astroport-pair-xastro/astroport-pair-xastro.json index b0e23af18..f025b92ac 100644 --- a/schemas/astroport-pair-xastro/astroport-pair-xastro.json +++ b/schemas/astroport-pair-xastro/astroport-pair-xastro.json @@ -406,6 +406,19 @@ } }, "additionalProperties": false + }, + { + "description": "Custom execute endpoints for extended pool implementations", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false } ], "definitions": { @@ -518,6 +531,10 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/astroport-pair-xastro/raw/execute.json b/schemas/astroport-pair-xastro/raw/execute.json index 443466010..3a6ac6f12 100644 --- a/schemas/astroport-pair-xastro/raw/execute.json +++ b/schemas/astroport-pair-xastro/raw/execute.json @@ -246,6 +246,19 @@ } }, "additionalProperties": false + }, + { + "description": "Custom execute endpoints for extended pool implementations", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false } ], "definitions": { @@ -358,6 +371,10 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/astroport-pair-xyk-sale-tax/astroport-pair-xyk-sale-tax.json b/schemas/astroport-pair-xyk-sale-tax/astroport-pair-xyk-sale-tax.json index 4f8939676..bc4062705 100644 --- a/schemas/astroport-pair-xyk-sale-tax/astroport-pair-xyk-sale-tax.json +++ b/schemas/astroport-pair-xyk-sale-tax/astroport-pair-xyk-sale-tax.json @@ -406,6 +406,19 @@ } }, "additionalProperties": false + }, + { + "description": "Custom execute endpoints for extended pool implementations", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false } ], "definitions": { @@ -518,6 +531,10 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/astroport-pair-xyk-sale-tax/raw/execute.json b/schemas/astroport-pair-xyk-sale-tax/raw/execute.json index 443466010..3a6ac6f12 100644 --- a/schemas/astroport-pair-xyk-sale-tax/raw/execute.json +++ b/schemas/astroport-pair-xyk-sale-tax/raw/execute.json @@ -246,6 +246,19 @@ } }, "additionalProperties": false + }, + { + "description": "Custom execute endpoints for extended pool implementations", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false } ], "definitions": { @@ -358,6 +371,10 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/astroport-pair/astroport-pair.json b/schemas/astroport-pair/astroport-pair.json index 9fd41a393..cecd84f9a 100644 --- a/schemas/astroport-pair/astroport-pair.json +++ b/schemas/astroport-pair/astroport-pair.json @@ -406,6 +406,19 @@ } }, "additionalProperties": false + }, + { + "description": "Custom execute endpoints for extended pool implementations", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false } ], "definitions": { @@ -518,6 +531,10 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/astroport-pair/raw/execute.json b/schemas/astroport-pair/raw/execute.json index 443466010..3a6ac6f12 100644 --- a/schemas/astroport-pair/raw/execute.json +++ b/schemas/astroport-pair/raw/execute.json @@ -246,6 +246,19 @@ } }, "additionalProperties": false + }, + { + "description": "Custom execute endpoints for extended pool implementations", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false } ], "definitions": { @@ -358,6 +371,10 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" From 649821277d5f6bd5aeeb591e081b78d2f41d9b49 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Wed, 12 Feb 2025 19:48:52 +0300 Subject: [PATCH 22/24] fix required for rust 1.75.0 --- contracts/pair_concentrated_duality/src/orderbook/execute.rs | 4 ++-- packages/astroport/src/querier.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/pair_concentrated_duality/src/orderbook/execute.rs b/contracts/pair_concentrated_duality/src/orderbook/execute.rs index 1c87438a4..7bb9e1832 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/execute.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/execute.rs @@ -73,10 +73,10 @@ pub fn process_cumulative_trade( } } - let fee_info = if let Some(fee_info) = fee_info { + let fee_info = if let Some(fee_info) = fee_info.cloned() { fee_info } else { - &query_fee_info( + query_fee_info( &deps.querier, &config.factory_addr, config.pair_info.pair_type.clone(), diff --git a/packages/astroport/src/querier.rs b/packages/astroport/src/querier.rs index 591143431..a4571356b 100644 --- a/packages/astroport/src/querier.rs +++ b/packages/astroport/src/querier.rs @@ -181,6 +181,7 @@ where } /// This structure holds parameters that describe the fee structure for a pool. +#[derive(Clone)] pub struct FeeInfo { /// The fee address pub fee_address: Option, From 91554d27529c91ea2ed845bbc80f010685235a2c Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Wed, 12 Feb 2025 20:13:41 +0300 Subject: [PATCH 23/24] convince clippy --- contracts/pair_concentrated_duality/src/migrate.rs | 2 +- contracts/pair_concentrated_duality/src/orderbook/state.rs | 4 ++-- contracts/pair_concentrated_duality/src/orderbook/utils.rs | 4 +++- packages/astroport_test/src/modules/neutron_stargate.rs | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/contracts/pair_concentrated_duality/src/migrate.rs b/contracts/pair_concentrated_duality/src/migrate.rs index 544bd7271..cc5ff8568 100644 --- a/contracts/pair_concentrated_duality/src/migrate.rs +++ b/contracts/pair_concentrated_duality/src/migrate.rs @@ -53,7 +53,7 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult StdError::generic_err(format!("This endpoint is allowed only for {CONTRACT_NAME}")) ); - Err(StdError::generic_err("Not yet implemented".to_string()).into()) + Err(StdError::generic_err("Not yet implemented".to_string())) } }?; diff --git a/contracts/pair_concentrated_duality/src/orderbook/state.rs b/contracts/pair_concentrated_duality/src/orderbook/state.rs index d4e0bf691..a712754e7 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/state.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/state.rs @@ -222,7 +222,7 @@ impl OrderbookState { maker_coin_out, }) => Ok([taker_coin_out, maker_coin_out] .into_iter() - .filter_map(|coin| coin) + .flatten() .collect_vec()), }) }) @@ -266,7 +266,7 @@ impl OrderbookState { .into_result() .map_err(|err| StdError::generic_err(err.to_string()))? .into_result() - .map_err(|err| StdError::generic_err(err))?; + .map_err(StdError::generic_err)?; self.orders = from_json::( &response_raw, diff --git a/contracts/pair_concentrated_duality/src/orderbook/utils.rs b/contracts/pair_concentrated_duality/src/orderbook/utils.rs index 4c936d21c..7c1435dc6 100644 --- a/contracts/pair_concentrated_duality/src/orderbook/utils.rs +++ b/contracts/pair_concentrated_duality/src/orderbook/utils.rs @@ -213,7 +213,9 @@ fn price_to_duality_notation( ) -> Result { let prec_diff = quote_precision as i8 - base_precision as i8; let price = match prec_diff.cmp(&0) { - Ordering::Less => price / Decimal256::from_integer(10u128.pow(prec_diff.abs() as u32)), + Ordering::Less => { + price / Decimal256::from_integer(10u128.pow(prec_diff.unsigned_abs() as u32)) + } Ordering::Equal => price, Ordering::Greater => price * Decimal256::from_integer(10u128.pow(prec_diff as u32)), } diff --git a/packages/astroport_test/src/modules/neutron_stargate.rs b/packages/astroport_test/src/modules/neutron_stargate.rs index 88e8051f8..aa7078f56 100644 --- a/packages/astroport_test/src/modules/neutron_stargate.rs +++ b/packages/astroport_test/src/modules/neutron_stargate.rs @@ -122,8 +122,8 @@ impl Module for NeutronStargate { self.orders .borrow_mut() .entry(sender.to_string()) - .or_insert_with(HashMap::new) - .insert(tranche_key, value.try_into()?); + .or_default() + .insert(tranche_key, value); Ok(AppResponse::default()) } MsgCancelLimitOrder::TYPE_URL => { From 4463c847071394ef94959635949633868487f026 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:17:11 +0300 Subject: [PATCH 24/24] cargo fmt --- packages/astroport/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astroport/src/lib.rs b/packages/astroport/src/lib.rs index 943684d23..f81ff716b 100644 --- a/packages/astroport/src/lib.rs +++ b/packages/astroport/src/lib.rs @@ -33,9 +33,9 @@ mod mock_querier; pub mod astro_converter; pub mod incentives; -pub mod pair_xastro; #[cfg(feature = "duality")] pub mod pair_concentrated_duality; +pub mod pair_xastro; #[cfg(test)] mod testing;