diff --git a/smart-contracts/Cargo.lock b/smart-contracts/Cargo.lock index 3f1167219..011f599f3 100644 --- a/smart-contracts/Cargo.lock +++ b/smart-contracts/Cargo.lock @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "apollo-cw-asset" @@ -57,17 +57,10 @@ dependencies = [ ] [[package]] -name = "apollo-utils" -version = "0.1.2" +name = "arrayvec" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e669b1a4c378832d63a87b3ca2efafc6f4881a61af5dd3e5a6b222c54f2a02ec" -dependencies = [ - "apollo-cw-asset", - "cosmwasm-schema", - "cosmwasm-std", - "cw20", - "regex", -] +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-trait" @@ -203,6 +196,18 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -227,6 +232,30 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.48", + "syn_derive", +] + [[package]] name = "bs58" version = "0.5.0" @@ -242,6 +271,28 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -281,6 +332,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.32" @@ -297,18 +354,17 @@ dependencies = [ "apollo-cw-asset", "cosmwasm-schema", "cosmwasm-std", - "cw-dex", - "cw-dex-router", "cw-storage-plus", "cw-utils", "cw-vault-multi-standard", "cw2", + "dex-router-osmosis", "num_enum", - "osmosis-std 0.24.0", + "osmosis-std", "osmosis-std-derive", "osmosis-test-tube", "proptest", - "prost 0.12.4", + "prost 0.12.6", "schemars", "serde", "thiserror", @@ -353,9 +409,20 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237" dependencies = [ - "prost 0.12.4", + "prost 0.12.6", + "prost-types 0.12.3", + "tendermint-proto 0.34.0", +] + +[[package]] +name = "cosmos-sdk-proto" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e23f6ab56d5f031cde05b8b82a5fefd3a1a223595c79e32317a97189e612bc" +dependencies = [ + "prost 0.12.6", "prost-types 0.12.3", - "tendermint-proto", + "tendermint-proto 0.35.0", ] [[package]] @@ -365,7 +432,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47126f5364df9387b9d8559dcef62e99010e1d4098f39eb3f7ee4b5c254e40ea" dependencies = [ "bip32", - "cosmos-sdk-proto", + "cosmos-sdk-proto 0.20.0", "ecdsa", "eyre", "k256", @@ -381,9 +448,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b4c3f9c4616d6413d4b5fc4c270a4cc32a374b9be08671e80e1a019f805d8f" +checksum = "dd50718a2b6830ce9eb5d465de5a018a12e71729d66b70807ce97e6dd14f931d" dependencies = [ "digest 0.10.7", "ecdsa", @@ -395,9 +462,9 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c586ced10c3b00e809ee664a895025a024f60d65d34fe4c09daed4a4db68a3f3" +checksum = "242e98e7a231c122e08f300d9db3262d1007b51758a8732cd6210b3e9faa4f3a" dependencies = [ "syn 1.0.109", ] @@ -428,9 +495,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712fe58f39d55c812f7b2c84e097cdede3a39d520f89b6dc3153837e31741927" +checksum = "78c1556156fdf892a55cced6115968b961eaaadd6f724a2c2cb7d1e168e32dd3" dependencies = [ "base64 0.21.7", "bech32 0.9.1", @@ -505,59 +572,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "cw-controllers" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57de8d3761e46be863e3ac1eba8c8a976362a48c6abf240df1e26c3e421ee9e8" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw-dex" -version = "0.1.3" -source = "git+https://github.com/quasar-finance/cw-dex?branch=feat/deprecate-osmo-gamm#ea2d7ec1dc8079016390370de9171b4c9c23488c" -dependencies = [ - "apollo-cw-asset", - "apollo-utils", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "cw20", - "osmosis-std 0.21.0", - "thiserror", -] - -[[package]] -name = "cw-dex-router" -version = "0.2.1-rc.1" -source = "git+https://github.com/quasar-finance/cw-dex-router?branch=feat/deprecate-osmo-gamm#19283e9b7f960fd586bb6537ff133ac5230ef623" -dependencies = [ - "apollo-cw-asset", - "apollo-utils", - "cosmwasm-schema", - "cosmwasm-std", - "cw-controllers", - "cw-dex", - "cw-storage-plus", - "cw2", - "cw20", - "thiserror", -] - [[package]] name = "cw-multi-test" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ffa9e3bae206540c084198e5be5aea2ecb1f2597f79dc09263b528ea0604788" +checksum = "91fc33b1d65c102d72f46548c64dca423c337e528d6747d0c595316aa65f887b" dependencies = [ "anyhow", "bech32 0.11.0", @@ -565,8 +584,8 @@ dependencies = [ "cw-storage-plus", "cw-utils", "derivative", - "itertools 0.12.1", - "prost 0.12.4", + "itertools 0.13.0", + "prost 0.12.6", "schemars", "serde", "sha2 0.10.8", @@ -650,23 +669,41 @@ dependencies = [ ] [[package]] -name = "deranged" -version = "0.3.11" +name = "derivative" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "powerfmt", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "derivative" -version = "2.2.0" +name = "derive_more" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.48", +] + +[[package]] +name = "dex-router-osmosis" +version = "0.0.1" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw2", + "mars-owner", + "osmosis-std", + "osmosis-test-tube", + "prost 0.12.6", + "quasar-types", + "thiserror", ] [[package]] @@ -855,6 +892,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.30" @@ -1149,6 +1192,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -1224,6 +1276,19 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "mars-owner" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab46e0b2f81a8a98036b46730fbe33a337e98e87cb3d34553b45a5ae87c5828c" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "thiserror", +] + [[package]] name = "memchr" version = "2.7.1" @@ -1300,6 +1365,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -1341,6 +1417,15 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.32.2" @@ -1370,30 +1455,14 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "osmosis-std" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87adf61f03306474ce79ab322d52dfff6b0bcf3aed1e12d8864ac0400dec1bf" -dependencies = [ - "chrono", - "cosmwasm-std", - "osmosis-std-derive", - "prost 0.12.4", - "prost-types 0.12.3", - "schemars", - "serde", - "serde-cw-value", -] - -[[package]] -name = "osmosis-std" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7a605885934d9bcfcd544f4e2098365725974522cbf8d696f65c5bb8047b3" +checksum = "ca66dca7e8c9b11b995cd41a44c038134ccca4469894d663d8a9452d6e716241" dependencies = [ "chrono", "cosmwasm-std", "osmosis-std-derive", - "prost 0.12.4", + "prost 0.12.6", "prost-types 0.12.3", "schemars", "serde", @@ -1415,16 +1484,16 @@ dependencies = [ [[package]] name = "osmosis-test-tube" -version = "24.0.1" +version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f234843e28ac9bddf7c45330c4f3b9a02045b67321dbbece1519a0f08466b1c" +checksum = "5eb35dcc9adc1b39e23dfae07c9f04a60187fde57a52b7762434ea6548581a1a" dependencies = [ "base64 0.21.7", "bindgen", "cosmrs", "cosmwasm-std", - "osmosis-std 0.24.0", - "prost 0.12.4", + "osmosis-std", + "prost 0.12.6", "serde", "serde_json", "test-tube", @@ -1518,12 +1587,6 @@ dependencies = [ "spki", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1549,6 +1612,29 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.78" @@ -1590,12 +1676,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive 0.12.4", + "prost-derive 0.12.6", ] [[package]] @@ -1613,9 +1699,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools 0.12.1", @@ -1639,7 +1725,48 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" dependencies = [ - "prost 0.12.4", + "prost 0.12.6", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quasar-types" +version = "0.1.0" +dependencies = [ + "cosmos-sdk-proto 0.21.1", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw20", + "derive_more", + "osmosis-std", + "osmosis-std-derive", + "prost 0.12.6", + "rust_decimal", + "schemars", + "serde", + "serde-json-wasm 1.0.1", + "serde_test", + "thiserror", ] [[package]] @@ -1657,6 +1784,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -1709,10 +1842,11 @@ dependencies = [ "cl-vault", "cosmwasm-schema", "cosmwasm-std", - "cw-dex-router", "cw-multi-test", "cw-storage-plus", "cw2", + "dex-router-osmosis", + "osmosis-std", "schemars", "serde", "thiserror", @@ -1756,6 +1890,15 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "reqwest" version = "0.11.23" @@ -1829,6 +1972,35 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "rkyv" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid 1.9.1", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rs_merkle" version = "1.4.2" @@ -1838,6 +2010,22 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "rust_decimal" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand", + "rkyv", + "serde", + "serde_json", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1944,9 +2132,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", @@ -1956,14 +2144,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.48", ] [[package]] @@ -1976,6 +2164,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "sec1" version = "0.7.3" @@ -2021,9 +2215,9 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.199" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -2066,9 +2260,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.199" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", @@ -2077,13 +2271,13 @@ dependencies = [ [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.48", ] [[package]] @@ -2108,6 +2302,15 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "serde_test" +version = "1.0.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a2f49ace1498612d14f7e0b8245519584db8299541dfe31a06374a828d620ab" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2160,6 +2363,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "slab" version = "0.4.9" @@ -2244,6 +2453,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -2265,6 +2486,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.9.0" @@ -2293,7 +2520,7 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost 0.12.4", + "prost 0.12.6", "prost-types 0.12.3", "ripemd", "serde", @@ -2304,7 +2531,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto", + "tendermint-proto 0.34.0", "time", "zeroize", ] @@ -2331,9 +2558,27 @@ checksum = "2cc728a4f9e891d71adf66af6ecaece146f9c7a11312288a3107b3e1d6979aaf" dependencies = [ "bytes", "flex-error", - "num-derive", + "num-derive 0.3.3", + "num-traits", + "prost 0.12.6", + "prost-types 0.12.3", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff525d5540a9fc535c38dc0d92a98da3ee36fcdfbda99cecb9f3cce5cd4d41d7" +dependencies = [ + "bytes", + "flex-error", + "num-derive 0.4.2", "num-traits", - "prost 0.12.4", + "prost 0.12.6", "prost-types 0.12.3", "serde", "serde_bytes", @@ -2363,27 +2608,27 @@ dependencies = [ "subtle-encoding", "tendermint", "tendermint-config", - "tendermint-proto", + "tendermint-proto 0.34.0", "thiserror", "time", "tokio", "tracing", "url", - "uuid", + "uuid 0.8.2", "walkdir", ] [[package]] name = "test-tube" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba5f5efe00b4ef18953dd869dfc0de260c16d6d9a54a3a8f3bfd2abd786592" +checksum = "804bb9bda992b6cda6f883e7973cb999d4da90d21257fb918d6a693407148681" dependencies = [ "base64 0.21.7", "cosmrs", "cosmwasm-std", - "osmosis-std 0.24.0", - "prost 0.12.4", + "osmosis-std", + "prost 0.12.6", "serde", "serde_json", "thiserror", @@ -2391,18 +2636,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", @@ -2411,31 +2656,20 @@ dependencies = [ [[package]] name = "time" -version = "0.3.31" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" dependencies = [ - "deranged", - "powerfmt", - "serde", - "time-core", + "libc", + "num_threads", "time-macros", ] -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - [[package]] name = "time-macros" -version = "0.2.16" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" -dependencies = [ - "time-core", -] +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" [[package]] name = "tinyvec" @@ -2617,6 +2851,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +[[package]] +name = "uuid" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" + [[package]] name = "version_check" version = "0.9.4" @@ -2927,6 +3167,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/smart-contracts/Cargo.toml b/smart-contracts/Cargo.toml index a7e556601..f41d68924 100644 --- a/smart-contracts/Cargo.toml +++ b/smart-contracts/Cargo.toml @@ -15,6 +15,7 @@ members = [ "contracts/cl-vault", "contracts/merkle-incentives", "contracts/range-middleware", + "contracts/dex-router-osmosis" ] [workspace.dependencies] @@ -31,13 +32,15 @@ cw20 = "1.1.2" cw20-base = {version = "1.1.2", features = ["library"]} cw20-staking = "0.11.1" apollo-cw-asset = "0.1.2" -cw-dex = {git = "https://github.com/quasar-finance/cw-dex", branch = "feat/deprecate-osmo-gamm"} +mars-owner = "2.0.0" +quasar-types = { path = "packages/quasar-types" } +dex-router-osmosis = { path = "contracts/dex-router-osmosis", features = ["library"] } # SDK cosmos-sdk-proto = {version = "0.21.1", default-features = false} # Osmosis -osmosis-std = "0.24.0" +osmosis-std = "0.25.0" osmosis-std-derive = "0.20.1" # Serialization & Tools @@ -52,6 +55,6 @@ num_enum = "0.7.2" base64 = "0.22.0" # Testing -cw-multi-test = "1.1.0" # {git = "https://github.com/njerschow/cw-multi-test.git"} -osmosis-test-tube = "24.0.1" +cw-multi-test = "1.2.0" +osmosis-test-tube = "25.0.0" proptest = "1.2.0" diff --git a/smart-contracts/contracts/README.md b/smart-contracts/contracts/README.md index e320629e1..c1bd74377 100644 --- a/smart-contracts/contracts/README.md +++ b/smart-contracts/contracts/README.md @@ -20,7 +20,7 @@ In the smart contracts directory, run: docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ -cosmwasm/workspace-optimizer:0.15.0 +cosmwasm/workspace-optimizer:0.16.0 ``` this builds all contracts in the contracts directory and places the wasm files in `smart-contracts/artifacts`. We need to use the workspace optimizer because we have a separate directory where our packages reside. diff --git a/smart-contracts/contracts/cl-vault/Cargo.toml b/smart-contracts/contracts/cl-vault/Cargo.toml index f823306e2..0aea584e0 100644 --- a/smart-contracts/contracts/cl-vault/Cargo.toml +++ b/smart-contracts/contracts/cl-vault/Cargo.toml @@ -36,7 +36,7 @@ library = [] optimize = """docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.15.0 + cosmwasm/rust-optimizer:0.16.0 """ [dependencies] @@ -53,11 +53,10 @@ cw-utils = { workspace = true } cw2 = { workspace = true } num_enum = { workspace = true } apollo-cw-asset = { workspace = true } +dex-router-osmosis = {workspace = true} +# cw-dex-osmosis = {workspace = true} ## todo update this in dex router code first as this has been deprecated -cw-dex = { workspace = true } - -cw-dex-router = {git = "https://github.com/quasar-finance/cw-dex-router", branch = "feat/deprecate-osmo-gamm", features = ["library", "osmosis"]} cw-vault-multi-standard = {git = "https://github.com/quasar-finance/cw-vault-standard", branch ="master", features = ["lockup", "force-unlock"]} [dev-dependencies] diff --git a/smart-contracts/contracts/cl-vault/src/contract.rs b/smart-contracts/contracts/cl-vault/src/contract.rs index 49f9dd389..b67acfafb 100644 --- a/smart-contracts/contracts/cl-vault/src/contract.rs +++ b/smart-contracts/contracts/cl-vault/src/contract.rs @@ -113,8 +113,7 @@ pub fn execute( max_slippage, ratio_of_swappable_funds_to_use, twap_window_seconds, - recommended_swap_route, - force_swap_route, + forced_swap_route, claim_after, }) => prepend_claim_msg( &env, @@ -127,15 +126,13 @@ pub fn execute( max_slippage, ratio_of_swappable_funds_to_use, twap_window_seconds, - recommended_swap_route, - force_swap_route, + forced_swap_route, claim_after, )?, ), - crate::msg::ExtensionExecuteMsg::SwapNonVaultFunds { - force_swap_route, - swap_routes, - } => execute_swap_non_vault_funds(deps, env, info, force_swap_route, swap_routes), + crate::msg::ExtensionExecuteMsg::SwapNonVaultFunds { swap_operations } => { + execute_swap_non_vault_funds(deps, env, info, swap_operations) + } crate::msg::ExtensionExecuteMsg::CollectRewards {} => { execute_collect_rewards(deps, env) } diff --git a/smart-contracts/contracts/cl-vault/src/error.rs b/smart-contracts/contracts/cl-vault/src/error.rs index 1bf29f559..3257949af 100644 --- a/smart-contracts/contracts/cl-vault/src/error.rs +++ b/smart-contracts/contracts/cl-vault/src/error.rs @@ -98,8 +98,8 @@ pub enum ContractError { #[error("Cannot force a recommended route if recommended route is passed in as None")] TryForceRouteWithoutRecommendedSwapRoute {}, - #[error("Auto compound swap list is empty")] - EmptyCompoundAssetList {}, + #[error("Swap operations for non vault funds swap cannot be empty")] + EmptySwapOperations {}, #[error("Migration status is closed")] MigrationStatusClosed {}, diff --git a/smart-contracts/contracts/cl-vault/src/helpers/getters.rs b/smart-contracts/contracts/cl-vault/src/helpers/getters.rs index 9e94ee8b3..c8cf9c37b 100644 --- a/smart-contracts/contracts/cl-vault/src/helpers/getters.rs +++ b/smart-contracts/contracts/cl-vault/src/helpers/getters.rs @@ -182,7 +182,6 @@ pub fn get_depositable_tokens( // let liquidity_y = delta_y.checked_div(denominator)?; -// // todo: check this is what we want // Ok(( // liquidity_x.atomics().try_into()?, // liquidity_y.atomics().try_into()?, diff --git a/smart-contracts/contracts/cl-vault/src/helpers/msgs.rs b/smart-contracts/contracts/cl-vault/src/helpers/msgs.rs index 726e38a19..67019dbf4 100644 --- a/smart-contracts/contracts/cl-vault/src/helpers/msgs.rs +++ b/smart-contracts/contracts/cl-vault/src/helpers/msgs.rs @@ -1,14 +1,8 @@ -use apollo_cw_asset::AssetInfo; use cosmwasm_std::{ attr, to_json_binary, Addr, Attribute, BankMsg, Coin, CosmosMsg, Deps, DepsMut, Env, Uint128, WasmMsg, }; -use cw_dex_router::{ - msg::{ - BestPathForPairResponse, ExecuteMsg as DexRouterExecuteMsg, QueryMsg as DexRouterQueryMsg, - }, - operations::SwapOperationsListUnchecked, -}; +use dex_router_osmosis::msg::ExecuteMsg as DexRouterExecuteMsg; use osmosis_std::types::{ cosmos::base::v1beta1::Coin as OsmoCoin, osmosis::{ @@ -89,77 +83,28 @@ pub fn swap_msg(deps: &DepsMut, env: &Env, params: SwapParams) -> Result deps.querier.query_wasm_smart( - dex_router_address.to_string(), - &DexRouterQueryMsg::SimulateSwapOperations { - offer_amount: params.token_in_amount, - operations, - }, - )?, - None => 0u128.into(), - }; - let best_path: Option = deps.querier.query_wasm_smart( - dex_router_address.to_string(), - &DexRouterQueryMsg::BestPathForPair { - offer_asset: offer_asset.into(), - ask_asset: ask_asset.into(), - exclude_paths: None, - offer_amount: params.token_in_amount, - }, - )?; - let best_out = match best_path.clone() { - Some(best_path) => best_path.return_amount, - None => 0u128.into(), - }; // if we need to force the route - if params.force_swap_route { - match params.recommended_swap_route { - Some(recommended_swap_route) => cw_dex_execute_swap_operations_msg( + if params.forced_swap_route.is_some() { + match params.forced_swap_route { + Some(forced_swap_route) => cw_dex_execute_swap_operations_msg( &dex_router_address, - recommended_swap_route, - params.token_out_min_amount, - ¶ms.token_in_denom.to_string(), + forced_swap_route, + params.token_in_denom.to_string(), params.token_in_amount, + params.token_out_denom.to_string(), + params.token_out_min_amount, ), None => Err(ContractError::TryForceRouteWithoutRecommendedSwapRoute {}), } - } else if best_out.is_zero() && recommended_out.is_zero() { - Ok(osmosis_swap_exact_amount_in_msg( - env, - pool_route, - params.token_in_amount, - ¶ms.token_in_denom.to_string(), - params.token_out_min_amount, - )) - } else if best_out.ge(&recommended_out) { - let operations = best_path - .ok_or(ContractError::MissingBestPath {})? - .operations - .into(); - cw_dex_execute_swap_operations_msg( - &dex_router_address, - operations, - params.token_out_min_amount, - ¶ms.token_in_denom.to_string(), - params.token_in_amount, - ) } else { - // recommended_out > best_out - let recommended_swap_route = params - .recommended_swap_route - .ok_or(ContractError::MissingRecommendedSwapRoute {})?; cw_dex_execute_swap_operations_msg( &dex_router_address, - recommended_swap_route, // will be some here - params.token_out_min_amount, - ¶ms.token_in_denom.to_string(), + vec![], // will be None here, should it be None? + params.token_in_denom.to_string(), params.token_in_amount, + params.token_out_denom.to_string(), + params.token_out_min_amount, ) } } @@ -185,21 +130,22 @@ fn osmosis_swap_exact_amount_in_msg( fn cw_dex_execute_swap_operations_msg( dex_router_address: &Addr, - operations: SwapOperationsListUnchecked, - token_out_min_amount: Uint128, - token_in_denom: &String, + path: Vec, + token_in_denom: String, token_in_amount: Uint128, + token_out_denom: String, + token_out_min_amount: Uint128, ) -> Result { let swap_msg: CosmosMsg = WasmMsg::Execute { contract_addr: dex_router_address.to_string(), - msg: to_json_binary(&DexRouterExecuteMsg::ExecuteSwapOperations { - operations, + msg: to_json_binary(&DexRouterExecuteMsg::Swap { + path: Some(path), + out_denom: token_out_denom, minimum_receive: Some(token_out_min_amount), to: None, - offer_amount: None, })?, funds: vec![Coin { - denom: token_in_denom.to_string(), + denom: token_in_denom, amount: token_in_amount, }], } diff --git a/smart-contracts/contracts/cl-vault/src/msg.rs b/smart-contracts/contracts/cl-vault/src/msg.rs index 6be686e91..80285bea9 100644 --- a/smart-contracts/contracts/cl-vault/src/msg.rs +++ b/smart-contracts/contracts/cl-vault/src/msg.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Decimal, Uint128}; -use cw_dex_router::operations::SwapOperationsListUnchecked; use cw_vault_multi_standard::{VaultStandardExecuteMsg, VaultStandardQueryMsg}; +use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; use crate::{ query::{ @@ -9,7 +9,6 @@ use crate::{ UserSharesBalanceResponse, VerifyTickCacheResponse, }, state::{Metadata, VaultConfig}, - vault::autocompound::SwapAsset, }; /// Extension execute messages for an apollo autocompounding vault @@ -31,10 +30,7 @@ pub enum ExtensionExecuteMsg { /// MigrationStep MigrationStep { amount_of_users: Uint128 }, /// SwapNonVaultFunds - SwapNonVaultFunds { - force_swap_route: bool, - swap_routes: Vec, - }, + SwapNonVaultFunds { swap_operations: Vec }, } /// Extension messages for Authz. This interface basically reexports certain vault functionality @@ -90,10 +86,8 @@ pub struct ModifyRangeMsg { pub ratio_of_swappable_funds_to_use: Decimal, /// twap window to use in seconds pub twap_window_seconds: u64, - /// recommended swap route to take - pub recommended_swap_route: Option, - /// whether or not to force the swap route - pub force_swap_route: bool, + /// forced swap route to take + pub forced_swap_route: Option>, /// claim_after optional field, if we off chain computed that incentives have some forfeit duration. this will be persisted in POSITION state pub claim_after: Option, } @@ -103,6 +97,16 @@ pub struct MergePositionMsg { pub position_ids: Vec, } +// struct used by swap.rs on swap non vault funds +#[cw_serde] +pub struct SwapOperation { + pub token_in_denom: String, + pub pool_id_0: u64, // the osmosis pool_id as mandatory to have at least the chance to swap on CL pools + pub pool_id_1: u64, // the osmosis pool_id as mandatory to have at least the chance to swap on CL pools + pub forced_swap_route_token_0: Option>, + pub forced_swap_route_token_1: Option>, +} + /// Extension query messages for an apollo autocompounding vault #[cw_serde] pub enum ExtensionQueryMsg { diff --git a/smart-contracts/contracts/cl-vault/src/state.rs b/smart-contracts/contracts/cl-vault/src/state.rs index d9decc81e..b5e247f98 100644 --- a/smart-contracts/contracts/cl-vault/src/state.rs +++ b/smart-contracts/contracts/cl-vault/src/state.rs @@ -4,8 +4,8 @@ use crate::{ }; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Decimal, Decimal256, Uint128}; -use cw_dex_router::operations::SwapOperationsListUnchecked; use cw_storage_plus::{Deque, Item, Map}; +use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; /// metadata useful for display purposes #[cw_serde] @@ -28,7 +28,7 @@ pub struct VaultConfig { pub performance_fee: Decimal, /// Account to receive fee payments pub treasury: Addr, - /// swap max slippage // TODO: This is unused. This should be used on any swap operations where we pass an offchain computed slippage parameter + /// swap max slippage pub swap_max_slippage: Decimal, /// Dex router address pub dex_router: Addr, @@ -61,7 +61,6 @@ pub const MIGRATION_STATUS: Item = Item::new("migration_status" #[cw_serde] pub struct PoolConfig { pub pool_id: u64, - // todo: Verify in instantiate message pub token0: String, pub token1: String, } @@ -150,9 +149,7 @@ pub struct ModifyRangeState { // the twap window to use for the swap in seconds pub twap_window_seconds: u64, // the recommended path to take for the swap - pub recommended_swap_route: Option, - // whether or not to force the swap route - pub force_swap_route: bool, + pub forced_swap_route: Option>, } pub const MODIFY_RANGE_STATE: Item> = Item::new("modify_range_state"); diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/autocompound.rs b/smart-contracts/contracts/cl-vault/src/test_tube/autocompound.rs index 3dd9e838d..fd6f526ac 100644 --- a/smart-contracts/contracts/cl-vault/src/test_tube/autocompound.rs +++ b/smart-contracts/contracts/cl-vault/src/test_tube/autocompound.rs @@ -6,18 +6,16 @@ mod tests { use std::ops::Sub; use std::str::FromStr; - use apollo_cw_asset::AssetInfoBase; use cosmwasm_std::assert_approx_eq; use cosmwasm_std::{Coin, Uint128}; - use cw_dex::osmosis::OsmosisPool; - use cw_dex_router::operations::SwapOperationBase; - use cw_dex_router::operations::SwapOperationsListUnchecked; use cw_vault_multi_standard::VaultStandardQueryMsg::VaultExtension; use osmosis_std::types::cosmos::bank::v1beta1::MsgSend; use osmosis_std::types::cosmos::base::v1beta1::Coin as OsmoCoin; + use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; use osmosis_test_tube::{Account, Bank, Module, Wasm}; use crate::msg::QueryMsg; + use crate::msg::SwapOperation; use crate::msg::UserBalanceQueryMsg::UserSharesBalance; use crate::msg::{ExecuteMsg, ExtensionQueryMsg}; use crate::query::AssetsBalanceResponse; @@ -32,13 +30,12 @@ mod tests { fixture_cw_dex_router, ACCOUNTS_INIT_BALANCE, ACCOUNTS_NUM, DENOM_BASE, DENOM_QUOTE, DENOM_REWARD, DEPOSIT_AMOUNT, }; - use crate::vault::autocompound::SwapAsset; const DENOM_REWARD_AMOUNT: u128 = 100000000000; #[test] #[ignore] - fn test_autocompound_with_rewards() { + fn test_autocompound_with_rewards_swap_non_vault_funds() { let ( app, contract_address, @@ -293,25 +290,6 @@ mod tests { // SWAP NON VAULT ASSETS BEFORE AUTOCOMPOUND ASSETS - // Define CW Dex Router swap routes - let path1 = vec![ - SwapOperationBase::new( - cw_dex::Pool::Osmosis(OsmosisPool::unchecked(swap_pools_ids[1])), - AssetInfoBase::Native(DENOM_REWARD.to_string()), - AssetInfoBase::Native(DENOM_QUOTE.to_string()), - ), - SwapOperationBase::new( - cw_dex::Pool::Osmosis(OsmosisPool::unchecked(swap_pools_ids[2])), - AssetInfoBase::Native(DENOM_QUOTE.to_string()), - AssetInfoBase::Native(DENOM_BASE.to_string()), - ), - ]; - let path2 = vec![SwapOperationBase::new( - cw_dex::Pool::Osmosis(OsmosisPool::unchecked(swap_pools_ids[1])), - AssetInfoBase::Native(DENOM_REWARD.to_string()), - AssetInfoBase::Native(DENOM_QUOTE.to_string()), - )]; - // Swap non vault funds to vault funds // 50000000000ustride to 49500000000uatom as spot price 1.0 less swap_fees // 50000000000ustride to 49500000000uosmo as spot price 1.0 less swap_fees @@ -320,17 +298,24 @@ mod tests { wasm.execute( contract_address.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::SwapNonVaultFunds { - force_swap_route: true, - swap_routes: vec![SwapAsset { + swap_operations: vec![SwapOperation { token_in_denom: DENOM_REWARD.to_string(), pool_id_0: swap_pools_ids[2], pool_id_1: swap_pools_ids[1], - recommended_swap_route_token_0: Option::from(SwapOperationsListUnchecked::new( - path1, - )), - recommended_swap_route_token_1: Option::from(SwapOperationsListUnchecked::new( - path2, - )), + forced_swap_route_token_0: Some(vec![ + SwapAmountInRoute { + pool_id: swap_pools_ids[1], + token_out_denom: DENOM_QUOTE.to_string(), + }, + SwapAmountInRoute { + pool_id: swap_pools_ids[2], + token_out_denom: DENOM_BASE.to_string(), + }, + ]), + forced_swap_route_token_1: Some(vec![SwapAmountInRoute { + pool_id: swap_pools_ids[1], + token_out_denom: DENOM_QUOTE.to_string(), + }]), }], }), &[], diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/deposit_withdraw.rs b/smart-contracts/contracts/cl-vault/src/test_tube/deposit_withdraw.rs index 18f497bdd..17268ab28 100644 --- a/smart-contracts/contracts/cl-vault/src/test_tube/deposit_withdraw.rs +++ b/smart-contracts/contracts/cl-vault/src/test_tube/deposit_withdraw.rs @@ -48,8 +48,6 @@ mod tests { ) .unwrap(); - // TODO: Check this -> Certain deposit amounts do not work here due to an off by one error in Osmosis cl code. The value here is chosen to specifically work - /* user:assets: AssetsBalanceResponse { balances: [Coin { 281243579389884 "uatom" }, Coin { 448554353093648 "uosmo" }] } 1_000_000_000_000_000 diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/initialize.rs b/smart-contracts/contracts/cl-vault/src/test_tube/initialize.rs index 4af963239..d731185c7 100644 --- a/smart-contracts/contracts/cl-vault/src/test_tube/initialize.rs +++ b/smart-contracts/contracts/cl-vault/src/test_tube/initialize.rs @@ -1,12 +1,8 @@ #[cfg(test)] pub mod initialize { - use apollo_cw_asset::{AssetInfoBase, AssetInfoUnchecked}; use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128}; - use cw_dex::osmosis::OsmosisPool; - use cw_dex_router::msg::ExecuteMsg as DexExecuteMsg; - use cw_dex_router::msg::InstantiateMsg as DexInstantiate; - use cw_dex_router::operations::{SwapOperationBase, SwapOperationsListUnchecked}; use cw_vault_multi_standard::VaultInfoResponse; + use dex_router_osmosis::msg::{ExecuteMsg as DexExecuteMsg, InstantiateMsg as DexInstantiate}; use osmosis_std::types::cosmos::base::v1beta1; use osmosis_std::types::osmosis::concentratedliquidity::v1beta1::{ CreateConcentratedLiquidityPoolsProposal, Pool, PoolRecord, PoolsRequest, @@ -144,7 +140,7 @@ pub mod initialize { ) { init_test_contract_with_dex_router_and_swap_pools( "./test-tube-build/wasm32-unknown-unknown/release/cl_vault.wasm", - "../../artifacts/cw_dex_router.wasm", + "../../artifacts/dex_router_osmosis.wasm", &[ Coin::new(ADMIN_BALANCE_AMOUNT, "uosmo"), Coin::new(ADMIN_BALANCE_AMOUNT, DENOM_BASE), @@ -545,19 +541,9 @@ pub mod initialize { wasm.execute( &dex_router, &DexExecuteMsg::SetPath { - offer_asset: AssetInfoUnchecked::Native( - pools_coins[index][0].denom.to_string(), - ), - ask_asset: AssetInfoUnchecked::Native(pools_coins[index][1].denom.to_string()), - path: SwapOperationsListUnchecked::new(vec![SwapOperationBase { - pool: cw_dex::Pool::Osmosis(OsmosisPool::unchecked(pool_id.clone())), - offer_asset_info: AssetInfoBase::Native( - pools_coins[index][0].denom.to_string(), - ), - ask_asset_info: AssetInfoBase::Native( - pools_coins[index][1].denom.to_string(), - ), - }]), + offer_denom: pools_coins[index][0].denom.to_string(), + ask_denom: pools_coins[index][1].denom.to_string(), + path: vec![*pool_id], bidirectional: true, }, vec![].as_ref(), @@ -565,31 +551,6 @@ pub mod initialize { ) .unwrap(); } - - // Set additional path - wasm.execute( - &dex_router, - &DexExecuteMsg::SetPath { - offer_asset: AssetInfoUnchecked::Native(DENOM_REWARD.to_string()), - ask_asset: AssetInfoUnchecked::Native(DENOM_BASE.to_string()), - path: SwapOperationsListUnchecked::new(vec![ - SwapOperationBase { - pool: cw_dex::Pool::Osmosis(OsmosisPool::unchecked(pools[1])), - ask_asset_info: AssetInfoBase::Native(DENOM_QUOTE.to_string()), - offer_asset_info: AssetInfoBase::Native(DENOM_REWARD.to_string()), - }, - SwapOperationBase { - pool: cw_dex::Pool::Osmosis(OsmosisPool::unchecked(pools[0])), - offer_asset_info: AssetInfoBase::Native(DENOM_QUOTE.to_string()), - ask_asset_info: AssetInfoBase::Native(DENOM_BASE.to_string()), - }, - ]), - bidirectional: true, - }, - sort_tokens(vec![]).as_ref(), - &admin, - ) - .unwrap(); } // TESTS @@ -674,8 +635,7 @@ pub mod initialize { max_slippage: Decimal::bps(MAX_SLIPPAGE_HIGH), ratio_of_swappable_funds_to_use: Decimal::one(), twap_window_seconds: 45, - recommended_swap_route: None, - force_swap_route: false, + forced_swap_route: None, claim_after: None, }, )), diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/proptest.rs b/smart-contracts/contracts/cl-vault/src/test_tube/proptest.rs index 52f674255..99cec0d1d 100644 --- a/smart-contracts/contracts/cl-vault/src/test_tube/proptest.rs +++ b/smart-contracts/contracts/cl-vault/src/test_tube/proptest.rs @@ -317,8 +317,7 @@ mod tests { max_slippage: Decimal::bps(MAX_SLIPPAGE_HIGH), // optimize and check how this fits in the strategy as it could trigger organic errors we dont want to test ratio_of_swappable_funds_to_use: Decimal::one(), twap_window_seconds: 45, - recommended_swap_route: None, - force_swap_route: false, + forced_swap_route: None, claim_after: None, }, )), diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/range.rs b/smart-contracts/contracts/cl-vault/src/test_tube/range.rs index bf5034020..390a29439 100644 --- a/smart-contracts/contracts/cl-vault/src/test_tube/range.rs +++ b/smart-contracts/contracts/cl-vault/src/test_tube/range.rs @@ -2,15 +2,15 @@ mod test { use std::str::FromStr; - use apollo_cw_asset::AssetInfoBase; use cosmwasm_std::{coin, Coin, Decimal, Uint128}; - use cw_dex::osmosis::OsmosisPool; - use cw_dex_router::operations::{SwapOperationBase, SwapOperationsListUnchecked}; use osmosis_std::types::{ cosmos::base::v1beta1, - osmosis::concentratedliquidity::{ - poolmodel::concentrated::v1beta1::MsgCreateConcentratedPool, - v1beta1::{MsgCreatePosition, Pool, PoolsRequest}, + osmosis::{ + concentratedliquidity::{ + poolmodel::concentrated::v1beta1::MsgCreateConcentratedPool, + v1beta1::{MsgCreatePosition, Pool, PoolsRequest}, + }, + poolmanager::v1beta1::SwapAmountInRoute, }, }; use osmosis_test_tube::{Account, ConcentratedLiquidity, Module, Wasm}; @@ -51,8 +51,7 @@ mod test { max_slippage: Decimal::bps(MAX_SLIPPAGE_HIGH), ratio_of_swappable_funds_to_use: Decimal::one(), twap_window_seconds: 45, - recommended_swap_route: None, - force_swap_route: false, + forced_swap_route: None, claim_after: None, }, )), @@ -74,6 +73,61 @@ mod test { #[test] #[ignore] fn move_range_cw_dex_works() { + let ( + app, + contract_address, + _dex_router_addr, + _vault_pool_id, + _swap_pools_ids, + admin, + _deposit_ratio, + _deposit_ratio_approx, + ) = fixture_cw_dex_router(PERFORMANCE_FEE_DEFAULT); + let wasm = Wasm::new(&app); + + let _before_position: PositionResponse = wasm + .query( + contract_address.as_str(), + &QueryMsg::VaultExtension(crate::msg::ExtensionQueryMsg::ConcentratedLiquidity( + crate::msg::ClQueryMsg::Position {}, + )), + ) + .unwrap(); + + let _result = wasm + .execute( + contract_address.as_str(), + &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::ModifyRange( + ModifyRangeMsg { + lower_price: Decimal::from_str("400").unwrap(), + upper_price: Decimal::from_str("1466").unwrap(), + max_slippage: Decimal::bps(MAX_SLIPPAGE_HIGH), + ratio_of_swappable_funds_to_use: Decimal::one(), + twap_window_seconds: 45, + // forced_swap_route: Some(vec![path1]), + forced_swap_route: None, + claim_after: None, + }, + )), + &[], + &admin, + ) + .unwrap(); + + let _after_position: PositionResponse = wasm + .query( + contract_address.as_str(), + &QueryMsg::VaultExtension(crate::msg::ExtensionQueryMsg::ConcentratedLiquidity( + crate::msg::ClQueryMsg::Position {}, + )), + ) + .unwrap(); + } + + // TODO: further enhance this forced swap test logic + #[test] + #[ignore] + fn move_range_cw_dex_works_forced_swap_route() { let ( app, contract_address, @@ -95,12 +149,12 @@ mod test { ) .unwrap(); - // Define CW Dex Router swap routes - let path1 = vec![SwapOperationBase::new( - cw_dex::Pool::Osmosis(OsmosisPool::unchecked(vault_pool_id)), - AssetInfoBase::Native(DENOM_QUOTE.to_string()), - AssetInfoBase::Native(DENOM_BASE.to_string()), - )]; + // Define CW Dex Router swap route to force + // In this case we are going from in range, to out of range to the upper side, so we swap all the quote token to base token + let path1 = SwapAmountInRoute { + pool_id: vault_pool_id, + token_out_denom: DENOM_BASE.to_string(), + }; let _result = wasm .execute( @@ -112,8 +166,7 @@ mod test { max_slippage: Decimal::bps(MAX_SLIPPAGE_HIGH), ratio_of_swappable_funds_to_use: Decimal::one(), twap_window_seconds: 45, - recommended_swap_route: Some(SwapOperationsListUnchecked::new(path1)), - force_swap_route: true, + forced_swap_route: Some(vec![path1]), claim_after: None, }, )), @@ -149,8 +202,7 @@ mod test { max_slippage: Decimal::bps(MAX_SLIPPAGE_HIGH), ratio_of_swappable_funds_to_use: Decimal::one(), twap_window_seconds: 45, - recommended_swap_route: None, - force_swap_route: false, + forced_swap_route: None, claim_after: None, }, )), @@ -169,8 +221,7 @@ mod test { max_slippage: Decimal::bps(MAX_SLIPPAGE_HIGH), ratio_of_swappable_funds_to_use: Decimal::one(), twap_window_seconds: 45, - recommended_swap_route: None, - force_swap_route: false, + forced_swap_route: None, claim_after: None, }, )), diff --git a/smart-contracts/contracts/cl-vault/src/vault/any_deposit.rs b/smart-contracts/contracts/cl-vault/src/vault/any_deposit.rs index 867259c22..14c4337f0 100644 --- a/smart-contracts/contracts/cl-vault/src/vault/any_deposit.rs +++ b/smart-contracts/contracts/cl-vault/src/vault/any_deposit.rs @@ -1,11 +1,7 @@ -use apollo_cw_asset::AssetInfoBase; use cosmwasm_std::{ attr, coin, Addr, Coin, Decimal, DepsMut, Env, Fraction, MessageInfo, Response, SubMsg, SubMsgResult, Uint128, Uint256, }; -use cw_dex::Pool::Osmosis; -use cw_dex_router::operations::{SwapOperationBase, SwapOperationsListUnchecked}; - use osmosis_std::types::osmosis::poolmanager::v1beta1::MsgSwapExactAmountInResponse; use osmosis_std::types::osmosis::tokenfactory::v1beta1::MsgMint; @@ -346,16 +342,7 @@ fn calculate_swap_amount( token_out_min_amount, token_in_denom: token_in_denom.clone(), token_out_denom: token_out_denom.clone(), - recommended_swap_route: Option::from(SwapOperationsListUnchecked::new(vec![ - SwapOperationBase { - pool: Osmosis(cw_dex::implementations::osmosis::OsmosisPool::unchecked( - pool_config.pool_id, - )), - offer_asset_info: AssetInfoBase::Native(token_in_denom.clone()), - ask_asset_info: AssetInfoBase::Native(token_out_denom.clone()), - }, - ])), - force_swap_route: false, + forced_swap_route: None, // TODO: check this None }, )?; diff --git a/smart-contracts/contracts/cl-vault/src/vault/autocompound.rs b/smart-contracts/contracts/cl-vault/src/vault/autocompound.rs index 761754d9f..96310bbb9 100644 --- a/smart-contracts/contracts/cl-vault/src/vault/autocompound.rs +++ b/smart-contracts/contracts/cl-vault/src/vault/autocompound.rs @@ -1,9 +1,7 @@ -use cosmwasm_schema::cw_serde; use cosmwasm_std::Order; use cosmwasm_std::{ to_json_binary, DepsMut, Env, MessageInfo, Response, SubMsg, SubMsgResult, Uint128, }; -use cw_dex_router::operations::SwapOperationsListUnchecked; use osmosis_std::cosmwasm_to_proto_coins; use osmosis_std::types::cosmos::bank::v1beta1::{Input, MsgMultiSend, Output}; use osmosis_std::types::osmosis::concentratedliquidity::v1beta1::ConcentratedliquidityQuerier; @@ -20,15 +18,6 @@ use crate::state::{MigrationStatus, MIGRATION_STATUS, POOL_CONFIG, POSITION}; use crate::vault::concentrated_liquidity::create_position; use crate::ContractError; -#[cw_serde] -pub struct SwapAsset { - pub token_in_denom: String, - pub pool_id_0: u64, // the osmosis pool_id as mandatory to have at least the chance to swap on CL pools - pub pool_id_1: u64, // the osmosis pool_id as mandatory to have at least the chance to swap on CL pools - pub recommended_swap_route_token_0: Option, - pub recommended_swap_route_token_1: Option, -} - pub fn execute_autocompound( deps: DepsMut, env: &Env, diff --git a/smart-contracts/contracts/cl-vault/src/vault/range.rs b/smart-contracts/contracts/cl-vault/src/vault/range.rs index 12cc4475e..5e0a7055e 100644 --- a/smart-contracts/contracts/cl-vault/src/vault/range.rs +++ b/smart-contracts/contracts/cl-vault/src/vault/range.rs @@ -25,12 +25,12 @@ use cosmwasm_std::{ attr, to_json_binary, Addr, Coin, Decimal, Decimal256, Deps, DepsMut, Env, Fraction, MessageInfo, Response, Storage, SubMsg, SubMsgResult, Uint128, }; -use cw_dex_router::operations::SwapOperationsListUnchecked; use osmosis_std::types::osmosis::{ concentratedliquidity::v1beta1::{ MsgCreatePositionResponse, MsgWithdrawPosition, MsgWithdrawPositionResponse, }, gamm::v1beta1::MsgSwapExactAmountInResponse, + poolmanager::v1beta1::SwapAmountInRoute, }; use std::str::FromStr; @@ -66,8 +66,7 @@ pub fn execute_update_range( max_slippage: Decimal, ratio_of_swappable_funds_to_use: Decimal, twap_window_seconds: u64, - recommended_swap_route: Option, - force_swap_route: bool, + forced_swap_route: Option>, claim_after: Option, ) -> Result { assert_range_admin(deps.storage, &info.sender)?; @@ -93,8 +92,7 @@ pub fn execute_update_range( new_range_position_ids: vec![], ratio_of_swappable_funds_to_use, twap_window_seconds, - recommended_swap_route, - force_swap_route, + forced_swap_route, }; execute_update_range_ticks(deps, env, info, modify_range_config, claim_after) @@ -208,7 +206,6 @@ pub fn handle_withdraw_position_reply( // creating the position here will fail because liquidityNeeded is calculated as 0 on chain level // we can fix this by going straight into a swap-deposit-merge before creating any positions - // todo: Check if needs LTE or just LT // 0 token0 and current_tick > lower_tick // 0 token1 and current_tick < upper_tick // if (lower < current < upper) && amount0 == 0 || amount1 == 0 @@ -488,8 +485,7 @@ fn calculate_swap_amount( token_out_min_amount, token_in_denom: token_in_denom.clone(), token_out_denom: token_out_denom.to_string(), - recommended_swap_route: mrs.recommended_swap_route, - force_swap_route: mrs.force_swap_route, + forced_swap_route: mrs.forced_swap_route, }, )?; @@ -747,7 +743,6 @@ mod tests { Decimal::one(), 45, None, - false, None, ) .unwrap(); @@ -812,7 +807,7 @@ mod tests { // .find(|a| { a.key == "token_in" }) // .unwrap() // .value, - // "5962token1" // TODO: number changed + // "5962token1" // ); // SECOND CASE STARTS HERE @@ -836,8 +831,7 @@ mod tests { max_slippage: Decimal::zero(), ratio_of_swappable_funds_to_use: Decimal::one(), twap_window_seconds: 45, - recommended_swap_route: None, - force_swap_route: false, + forced_swap_route: None, }), ) .unwrap(); diff --git a/smart-contracts/contracts/cl-vault/src/vault/swap.rs b/smart-contracts/contracts/cl-vault/src/vault/swap.rs index 152e748c3..79156ea15 100644 --- a/smart-contracts/contracts/cl-vault/src/vault/swap.rs +++ b/smart-contracts/contracts/cl-vault/src/vault/swap.rs @@ -1,11 +1,11 @@ use cosmwasm_std::{CosmosMsg, DepsMut, Env, Fraction, MessageInfo, Response, Uint128}; -use cw_dex_router::operations::SwapOperationsListUnchecked; +use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; use crate::helpers::msgs::swap_msg; +use crate::msg::SwapOperation; use crate::state::POOL_CONFIG; use crate::{state::VAULT_CONFIG, ContractError}; -use super::autocompound::SwapAsset; use super::range::assert_range_admin; /// SwapCalculationResult holds the result of a swap calculation @@ -24,30 +24,29 @@ pub struct SwapParams { pub token_in_denom: String, pub token_out_min_amount: Uint128, pub token_out_denom: String, - pub recommended_swap_route: Option, - pub force_swap_route: bool, + pub forced_swap_route: Option>, } pub fn execute_swap_non_vault_funds( deps: DepsMut, env: Env, info: MessageInfo, - force_swap_route: bool, - swap_assets: Vec, + swap_operations: Vec, ) -> Result { // validate auto compound admin as the purpose of swaps are mainly around autocompound non-vault assets into assets that can be actually compounded. assert_range_admin(deps.storage, &info.sender)?; let vault_config = VAULT_CONFIG.load(deps.storage)?; let pool_config = POOL_CONFIG.load(deps.storage)?; - if swap_assets.is_empty() { - return Err(ContractError::EmptyCompoundAssetList {}); + + if swap_operations.is_empty() { + return Err(ContractError::EmptySwapOperations {}); } let mut swap_msgs: Vec = vec![]; - for current_swap_asset in swap_assets { - let token_in_denom = current_swap_asset.token_in_denom.clone(); + for swap_operation in swap_operations { + let token_in_denom = swap_operation.token_in_denom.clone(); let pool_token_0 = pool_config.token0.clone(); let pool_token_1 = pool_config.token1.clone(); @@ -61,7 +60,7 @@ pub fn execute_swap_non_vault_funds( .querier .query_balance( env.clone().contract.address, - current_swap_asset.clone().token_in_denom, + swap_operation.clone().token_in_denom, )? .amount; @@ -77,7 +76,7 @@ pub fn execute_swap_non_vault_funds( .checked_add(Uint128::new(1))? .checked_div(Uint128::new(2))?; - // TODO: We should be passing the max_slippage from outside as we do during ModifyRange + // TODO_FUTURE: We should be passing the max_slippage from outside as we do during ModifyRange let token_out_min_amount_0 = part_0_amount.checked_multiply_ratio( vault_config.swap_max_slippage.numerator(), vault_config.swap_max_slippage.denominator(), @@ -91,26 +90,24 @@ pub fn execute_swap_non_vault_funds( &deps, &env, SwapParams { - pool_id: current_swap_asset.pool_id_0, + pool_id: swap_operation.pool_id_0, token_in_amount: part_0_amount, token_in_denom: token_in_denom.clone(), token_out_min_amount: token_out_min_amount_0, token_out_denom: pool_token_0, - recommended_swap_route: current_swap_asset.recommended_swap_route_token_0, - force_swap_route, + forced_swap_route: swap_operation.forced_swap_route_token_0, }, )?); swap_msgs.push(swap_msg( &deps, &env, SwapParams { - pool_id: current_swap_asset.pool_id_1, + pool_id: swap_operation.pool_id_1, token_in_amount: part_1_amount, token_in_denom: token_in_denom.clone(), token_out_min_amount: token_out_min_amount_1, token_out_denom: pool_token_1, - recommended_swap_route: current_swap_asset.recommended_swap_route_token_1, - force_swap_route, + forced_swap_route: swap_operation.forced_swap_route_token_1, }, )?); } @@ -157,7 +154,6 @@ pub fn execute_swap_non_vault_funds( // let pm_querier = // osmosis_std::types::osmosis::poolmanager::v1beta1::PoolmanagerQuerier::new(querier); -// // todo: verify that we should be concatenating amount and denom or if we should just send token in amount as string // let result = pm_querier.estimate_swap_exact_amount_in( // pool_config.pool_id, // token_in_amount.to_string() + token_in_denom, @@ -212,8 +208,7 @@ mod tests { token_out_min_amount, token_in_denom, token_out_denom, - recommended_swap_route: None, - force_swap_route: false, + forced_swap_route: None, }; let result = super::swap_msg(&deps_mut, &env, swap_params).unwrap(); diff --git a/smart-contracts/contracts/dex-router-osmosis/.cargo/config.toml b/smart-contracts/contracts/dex-router-osmosis/.cargo/config.toml new file mode 100644 index 000000000..05f844031 --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/.cargo/config.toml @@ -0,0 +1,6 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" +test-tube = "test --package cw-dex-router --lib -- --include-ignored test_tube:: --nocapture --test-threads=1" +test-tube-build = "build --release --lib --target wasm32-unknown-unknown --target-dir ./test-tube-build" diff --git a/smart-contracts/contracts/dex-router-osmosis/.github/workflows/Basic.yml b/smart-contracts/contracts/dex-router-osmosis/.github/workflows/Basic.yml new file mode 100644 index 000000000..3890a07cf --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/.github/workflows/Basic.yml @@ -0,0 +1,75 @@ +# Based on https://github.com/actions-rs/example/blob/master/.github/workflows/quickstart.yml + +on: [push, pull_request] + +name: Basic + +jobs: + + test: + name: Test Suite + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.60.0 + target: wasm32-unknown-unknown + override: true + + - name: Run unit tests + uses: actions-rs/cargo@v1 + with: + command: unit-test + args: --locked + env: + RUST_BACKTRACE: 1 + + - name: Compile WASM contract + uses: actions-rs/cargo@v1 + with: + command: wasm + args: --locked + env: + RUSTFLAGS: "-C link-arg=-s" + + lints: + name: Lints + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.60.0 + override: true + components: rustfmt, clippy + + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: -- -D warnings + + - name: Generate Schema + uses: actions-rs/cargo@v1 + with: + command: schema + args: --locked + + - name: Schema Changes + # fails if any changes not committed + run: git diff --exit-code schema diff --git a/smart-contracts/contracts/dex-router-osmosis/Cargo.toml b/smart-contracts/contracts/dex-router-osmosis/Cargo.toml new file mode 100644 index 000000000..44a9523de --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/Cargo.toml @@ -0,0 +1,39 @@ +[package] +authors = ["Quasar"] +edition = "2021" +name = "dex-router-osmosis" +version = "0.0.1" +readme = "README.md" +repository = "https://github.com/quasar-finance/quasar" +homepage = "https://quasar.fi" +documentation = "" +license = "MPL-2.0" +description = "A cosmwasm contract for routing swaps" +keywords = ["cosmwasm", "dex", "router", "osmosis"] + +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. + "dex_router_osmosis.wasm", + "hash.txt", +] + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = [] +library = [] + +[dependencies] +cosmwasm-std = { workspace = true } +cosmwasm-schema = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +thiserror = { workspace = true } +mars-owner = { workspace = true } +osmosis-std = { workspace = true } +quasar-types = { workspace = true } +prost = { workspace = true } + +[dev-dependencies] +osmosis-test-tube = { workspace = true } \ No newline at end of file diff --git a/smart-contracts/contracts/dex-router-osmosis/README.md b/smart-contracts/contracts/dex-router-osmosis/README.md new file mode 100644 index 000000000..2b1c9fb8f --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/README.md @@ -0,0 +1,9 @@ +# Dex router for osmosis + +Responsible for the on-chain computation of multi-hop swap routes. +Currently only supports pools with 2 tokens. + +TODO: + * Fix queries for best path + * Add checks and unit-tests + * Add support for (optional) on-chain detection of the optimal swap path \ No newline at end of file diff --git a/smart-contracts/contracts/dex-router-osmosis/examples/schema.rs b/smart-contracts/contracts/dex-router-osmosis/examples/schema.rs new file mode 100644 index 000000000..df4288066 --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/examples/schema.rs @@ -0,0 +1,17 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; + +use dex_router_osmosis::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); +} diff --git a/smart-contracts/contracts/dex-router-osmosis/schema/execute_msg.json b/smart-contracts/contracts/dex-router-osmosis/schema/execute_msg.json new file mode 100644 index 000000000..5ceaf850b --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/schema/execute_msg.json @@ -0,0 +1,151 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "swap" + ], + "properties": { + "swap": { + "type": "object", + "required": [ + "out_denom" + ], + "properties": { + "minimum_receive": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "out_denom": { + "type": "string" + }, + "path": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/SwapAmountInRoute" + } + }, + "to": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "set_path" + ], + "properties": { + "set_path": { + "type": "object", + "required": [ + "ask_denom", + "bidirectional", + "offer_denom", + "path" + ], + "properties": { + "ask_denom": { + "type": "string" + }, + "bidirectional": { + "type": "boolean" + }, + "offer_denom": { + "type": "string" + }, + "path": { + "type": "array", + "items": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "remove_path" + ], + "properties": { + "remove_path": { + "type": "object", + "required": [ + "ask_denom", + "bidirectional", + "offer_denom", + "path" + ], + "properties": { + "ask_denom": { + "type": "string" + }, + "bidirectional": { + "type": "boolean" + }, + "offer_denom": { + "type": "string" + }, + "path": { + "type": "array", + "items": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "SwapAmountInRoute": { + "type": "object", + "required": [ + "pool_id", + "token_out_denom" + ], + "properties": { + "pool_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token_out_denom": { + "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" + } + } +} diff --git a/smart-contracts/contracts/dex-router-osmosis/schema/instantiate_msg.json b/smart-contracts/contracts/dex-router-osmosis/schema/instantiate_msg.json new file mode 100644 index 000000000..1352613d5 --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/schema/instantiate_msg.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "additionalProperties": false +} diff --git a/smart-contracts/contracts/dex-router-osmosis/schema/query_msg.json b/smart-contracts/contracts/dex-router-osmosis/schema/query_msg.json new file mode 100644 index 000000000..ad878f362 --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/schema/query_msg.json @@ -0,0 +1,168 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "simulate_swaps" + ], + "properties": { + "simulate_swaps": { + "type": "object", + "required": [ + "offer", + "path" + ], + "properties": { + "offer": { + "$ref": "#/definitions/Coin" + }, + "path": { + "type": "array", + "items": { + "$ref": "#/definitions/SwapAmountInRoute" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns all the current path for a given (offer_asset, ask_asset) pair.", + "type": "object", + "required": [ + "paths_for_pair" + ], + "properties": { + "paths_for_pair": { + "type": "object", + "required": [ + "ask_denom", + "offer_denom" + ], + "properties": { + "ask_denom": { + "type": "string" + }, + "offer_denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "finds the best path for a given (offer_asset, ask_asset) pair. if no path is found, returns None.", + "type": "object", + "required": [ + "best_path_for_pair" + ], + "properties": { + "best_path_for_pair": { + "type": "object", + "required": [ + "ask_denom", + "offer" + ], + "properties": { + "ask_denom": { + "type": "string" + }, + "offer": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns all the assets from which there are paths to a given ask asset.", + "type": "object", + "required": [ + "supported_offer_assets" + ], + "properties": { + "supported_offer_assets": { + "type": "object", + "required": [ + "ask_denom" + ], + "properties": { + "ask_denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns all the assets to which there are paths from a given offer asset.", + "type": "object", + "required": [ + "supported_ask_assets" + ], + "properties": { + "supported_ask_assets": { + "type": "object", + "required": [ + "offer_denom" + ], + "properties": { + "offer_denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "SwapAmountInRoute": { + "type": "object", + "required": [ + "pool_id", + "token_out_denom" + ], + "properties": { + "pool_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token_out_denom": { + "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" + } + } +} diff --git a/smart-contracts/contracts/dex-router-osmosis/src/contract.rs b/smart-contracts/contracts/dex-router-osmosis/src/contract.rs new file mode 100644 index 000000000..d229b0c13 --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/src/contract.rs @@ -0,0 +1,438 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_json_binary, BankMsg, Binary, Coin, Deps, DepsMut, Env, Event, MessageInfo, Order, Reply, + Response, StdError, StdResult, SubMsg, Uint128, +}; +use cw2::set_contract_version; +use mars_owner::OwnerInit::SetInitialOwner; +use osmosis_std::cosmwasm_to_proto_coins; +use osmosis_std::types::osmosis::poolmanager::v1beta1::{ + MsgSwapExactAmountIn, PoolmanagerQuerier, SwapAmountInRoute, TotalPoolLiquidityResponse, +}; + +use quasar_types::error::assert_fund_length; +use std::str::FromStr; + +use crate::error::{assert_non_empty_path, ContractError}; +use crate::msg::{BestPathForPairResponse, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use crate::state::{RecipientInfo, OWNER, PATHS, RECIPIENT_INFO}; + +const _CONTRACT_NAME: &str = "quasar:dex-router-osmosis"; +const CONTRACT_NAME: &str = "crates.io:dex-router-osmosis"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +const SWAP_REPLY_ID: u64 = 1; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + _msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + OWNER.initialize( + deps.storage, + deps.api, + SetInitialOwner { + owner: info.sender.to_string(), + }, + )?; + + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Swap { + path, + out_denom, + minimum_receive, + to, + } => swap(deps, env, info, path, out_denom, minimum_receive, to), + ExecuteMsg::SetPath { + offer_denom, + ask_denom, + path, + bidirectional, + } => set_path(deps, info, offer_denom, ask_denom, path, bidirectional), + ExecuteMsg::RemovePath { + offer_denom, + ask_denom, + path, + bidirectional, + } => remove_path(deps, info, offer_denom, ask_denom, path, bidirectional), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, env: Env, reply: Reply) -> Result { + match reply.id { + SWAP_REPLY_ID => { + let recipient_info = RECIPIENT_INFO.load(deps.storage)?; + let balance = deps + .querier + .query_balance(env.contract.address.to_string(), recipient_info.denom)?; + RECIPIENT_INFO.remove(deps.storage); + Ok(Response::default().add_message(BankMsg::Send { + to_address: recipient_info.address.to_string(), + amount: vec![balance], + })) + } + _ => panic!("not implemented"), + } +} + +pub fn swap( + deps: DepsMut, + env: Env, + info: MessageInfo, + path: Option>, + out_denom: String, + minimum_receive: Option, + to: Option, +) -> Result { + assert_fund_length(info.funds.len(), 1)?; + let recipient = to.map_or(Ok(info.sender.clone()), |x| deps.api.addr_validate(&x))?; + let swap_path = if let Some(path) = path { + path + } else { + if let Some(best_path) = + query_best_path_for_pair(&deps.as_ref(), info.funds[0].clone(), out_denom.clone())? + { + best_path.path + } else { + return Err(ContractError::NoPathFound { + offer: info.funds[0].denom.clone(), + ask: out_denom, + }); + } + }; + assert_non_empty_path(&swap_path)?; + + let msg = MsgSwapExactAmountIn { + sender: env.contract.address.to_string(), + routes: swap_path, + token_in: Some(cosmwasm_to_proto_coins(info.funds.clone())[0].clone()), + token_out_min_amount: minimum_receive.unwrap_or_default().to_string(), + }; + RECIPIENT_INFO.save( + deps.storage, + &RecipientInfo { + address: info.sender, + denom: out_denom, + }, + )?; + let event = Event::new(_CONTRACT_NAME) + .add_attribute("operation", "swap") + .add_attribute("offer_amount", info.funds[0].amount) + .add_attribute("to", recipient.to_string()); + Ok(Response::new() + .add_submessage(SubMsg::reply_on_success(msg, SWAP_REPLY_ID)) + .add_event(event)) +} + +fn get_denoms(deps: &Deps, path: &[u64]) -> StdResult> { + let pool_querier = PoolmanagerQuerier::new(&deps.querier); + let liquidity: Result, StdError> = path + .iter() + .map(|pool_id| pool_querier.total_pool_liquidity(*pool_id)) + .collect(); + let liquidity = liquidity?; + Ok(liquidity + .into_iter() + .map(|liq| { + ( + liq.liquidity[0].denom.clone(), + liq.liquidity[1].denom.clone(), + ) + }) + .collect()) +} + +pub fn set_path( + deps: DepsMut, + info: MessageInfo, + offer_denom: String, + ask_denom: String, + path: Vec, + bidirectional: bool, +) -> Result { + OWNER.assert_owner(deps.storage, &info.sender)?; + assert_non_empty_path(&path)?; + let denoms = get_denoms(&deps.as_ref(), &path)?; + let key = (offer_denom.clone(), ask_denom.clone()); + + let mut offer_denom = offer_denom; + let mut out_denoms = vec![]; + let mut in_denoms = vec![]; + for denom_pair in &denoms { + in_denoms.push(offer_denom.clone()); + if offer_denom == denom_pair.0 { + offer_denom = denom_pair.1.clone(); + } else if offer_denom == denom_pair.1 { + offer_denom = denom_pair.0.clone(); + } else { + return Err(ContractError::InvalidSwapPath { + path, + reason: format!( + "Could not find {}, available denoms: {:?}", + offer_denom, denom_pair + ), + }); + } + out_denoms.push(offer_denom.clone()); + } + if offer_denom != ask_denom { + return Err(ContractError::InvalidSwapPath { + path, + reason: format!( + "Could not find {}, available denoms: {:?}", + ask_denom, + denoms.last().unwrap() + ), + }); + } + + let mut new_paths = vec![path + .iter() + .zip(out_denoms.iter()) + .map(|(pool_id, denom)| SwapAmountInRoute { + pool_id: *pool_id, + token_out_denom: denom.clone(), + }) + .collect()]; + PATHS.update(deps.storage, key.clone(), |paths| -> StdResult<_> { + if let Some(paths) = paths { + new_paths.extend(paths.into_iter()); + } + Ok(new_paths) + })?; + + let mut event = Event::new(_CONTRACT_NAME) + .add_attribute("operation", "set path") + .add_attribute("key", format!("{:?}", key)) + .add_attribute( + "path", + path.iter() + .map(|pool_id| pool_id.to_string()) + .collect::>() + .join(","), + ); + if bidirectional { + let mut new_paths = vec![path + .iter() + .rev() + .zip(in_denoms.iter().rev()) + .map(|(pool_id, denom)| SwapAmountInRoute { + pool_id: *pool_id, + token_out_denom: denom.clone(), + }) + .collect()]; + let reverse_key = (key.1, key.0); + PATHS.update(deps.storage, reverse_key.clone(), |paths| -> StdResult<_> { + if let Some(paths) = paths { + new_paths.extend(paths.into_iter()); + } + Ok(new_paths) + })?; + event = event.add_attribute("key", format!("{:?}", reverse_key)); + } + + Ok(Response::default().add_event(event)) +} + +fn try_remove_path( + paths: Option>>, + path: &[u64], + offer_denom: String, + ask_denom: String, +) -> Result>>, ContractError> { + if let Some(mut paths) = paths { + let idx = paths.iter().position(|p| -> bool { + path.iter() + .zip(p.iter().map(|p| p.pool_id)) + .all(|(pool_id0, pool_id1)| pool_id0 == &pool_id1) + }); + if let Some(idx) = idx { + paths.remove(idx); + if !paths.is_empty() { + return Ok(Some(paths)); + } else { + return Ok(None); + } + } + } + + return Err(ContractError::NoPathFound { + offer: offer_denom, + ask: ask_denom, + }); +} + +pub fn remove_path( + deps: DepsMut, + info: MessageInfo, + offer_denom: String, + ask_denom: String, + path: Vec, + bidirectional: bool, +) -> Result { + OWNER.assert_owner(deps.storage, &info.sender)?; + assert_non_empty_path(&path)?; + + let key = (offer_denom.clone(), ask_denom.clone()); + let paths = PATHS.may_load(deps.storage, key.clone())?; + let paths = try_remove_path(paths, &path, offer_denom.clone(), ask_denom.clone())?; + if let Some(paths) = paths { + PATHS.save(deps.storage, key.clone(), &paths)?; + } else { + PATHS.remove(deps.storage, key.clone()); + } + + let mut event = Event::new(_CONTRACT_NAME) + .add_attribute("operation", "remove path") + .add_attribute("key", format!("{:?}", key)) + .add_attribute( + "path", + path.iter() + .map(|pool_id| pool_id.to_string()) + .collect::>() + .join(","), + ); + if bidirectional { + let reverse_key = (ask_denom.clone(), offer_denom.clone()); + let paths = PATHS.may_load(deps.storage, reverse_key.clone())?; + let mut path = path; + path.reverse(); + let paths = try_remove_path(paths, &path, ask_denom.clone(), offer_denom.clone())?; + if let Some(paths) = paths { + PATHS.save(deps.storage, reverse_key.clone(), &paths)?; + } else { + PATHS.remove(deps.storage, reverse_key.clone()); + } + event = event.add_attribute("key", format!("{:?}", reverse_key)); + } + + Ok(Response::default().add_event(event)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { + match msg { + QueryMsg::SimulateSwaps { offer, path } => { + Ok(to_json_binary(&simulate_swaps(&deps, &offer, path)?)?) + } + QueryMsg::PathsForPair { + offer_denom, + ask_denom, + } => Ok(to_json_binary(&query_paths_for_pair( + &deps, + offer_denom, + ask_denom, + )?)?), + QueryMsg::BestPathForPair { offer, ask_denom } => Ok(to_json_binary( + &query_best_path_for_pair(&deps, offer, ask_denom)?, + )?), + QueryMsg::SupportedOfferAssets { ask_denom } => Ok(to_json_binary( + &query_supported_offer_assets(deps, ask_denom)?, + )?), + QueryMsg::SupportedAskAssets { offer_denom } => Ok(to_json_binary( + &query_supported_ask_assets(deps, offer_denom)?, + )?), + } +} + +pub fn simulate_swaps( + deps: &Deps, + offer: &Coin, + path: Vec, +) -> Result { + let querier = PoolmanagerQuerier::new(&deps.querier); + let response = querier.estimate_swap_exact_amount_in(0, offer.to_string(), path)?; + Ok(Uint128::from_str(&response.token_out_amount)?) +} + +pub fn query_paths_for_pair( + deps: &Deps, + offer_denom: String, + ask_denom: String, +) -> Result>, ContractError> { + let paths = PATHS.may_load(deps.storage, (offer_denom.clone(), ask_denom.clone()))?; + if let Some(paths) = paths { + if !paths.is_empty() { + return Ok(paths); + } + } + + Err(ContractError::NoPathFound { + offer: offer_denom, + ask: ask_denom, + }) +} + +pub fn query_best_path_for_pair( + deps: &Deps, + offer: Coin, + ask_denom: String, +) -> Result, ContractError> { + let paths = query_paths_for_pair(deps, offer.denom.clone(), ask_denom)?; + if paths.is_empty() { + return Err(ContractError::NoPathsToCheck {}); + } + let swap_paths: Result, ContractError> = paths + .into_iter() + .map(|path| { + let out = simulate_swaps(deps, &offer, path.clone())?; + Ok(BestPathForPairResponse { + path, + return_amount: out, + }) + }) + .collect(); + + let best_path = swap_paths? + .into_iter() + .max_by(|a, b| a.return_amount.cmp(&b.return_amount)); + + Ok(best_path) +} + +pub fn query_supported_offer_assets( + deps: Deps, + ask_denom: String, +) -> Result, ContractError> { + let mut offer_denoms: Vec = vec![]; + for x in PATHS.range(deps.storage, None, None, Order::Ascending) { + let ((offer_denom, path_ask_denom), _) = x?; + if path_ask_denom == ask_denom { + offer_denoms.push(offer_denom); + } + } + Ok(offer_denoms) +} + +pub fn query_supported_ask_assets( + deps: Deps, + offer_denom: String, +) -> Result, ContractError> { + let mut ask_denoms: Vec = vec![]; + for x in PATHS.range(deps.storage, None, None, Order::Ascending) { + let ((path_offer_denom, ask_denom), _) = x?; + if path_offer_denom == offer_denom { + ask_denoms.push(ask_denom); + } + } + Ok(ask_denoms) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + Ok(Response::default()) +} diff --git a/smart-contracts/contracts/dex-router-osmosis/src/error.rs b/smart-contracts/contracts/dex-router-osmosis/src/error.rs new file mode 100644 index 000000000..87bfa8db7 --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/src/error.rs @@ -0,0 +1,41 @@ +use cosmwasm_std::{OverflowError, StdError}; +use mars_owner::OwnerError; +use quasar_types::error::FundsError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + Overflow(#[from] OverflowError), + + #[error("{0}")] + Owner(#[from] OwnerError), + + #[error("{0}")] + Funds(#[from] FundsError), + + #[error("Invalid swap path: {path:?} {reason}")] + InvalidSwapPath { path: Vec, reason: String }, + + #[error("No path found for assets {offer:?} -> {ask:?}")] + NoPathFound { offer: String, ask: String }, + + #[error("No paths to check")] + NoPathsToCheck {}, + + #[error("Pool not found: {pool_id:?}")] + PoolNotFound { pool_id: u64 }, + + #[error("Can't set empty path.")] + EmptyPath {}, +} + +pub fn assert_non_empty_path(path: &[T]) -> Result<(), ContractError> { + if path.is_empty() { + return Err(ContractError::EmptyPath {}); + } + Ok(()) +} diff --git a/smart-contracts/contracts/dex-router-osmosis/src/lib.rs b/smart-contracts/contracts/dex-router-osmosis/src/lib.rs new file mode 100644 index 000000000..69b809fe1 --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/src/lib.rs @@ -0,0 +1,9 @@ +pub mod contract; +mod error; +pub mod msg; +pub mod state; + +pub use crate::error::ContractError; + +#[cfg(test)] +mod tests; diff --git a/smart-contracts/contracts/dex-router-osmosis/src/msg.rs b/smart-contracts/contracts/dex-router-osmosis/src/msg.rs new file mode 100644 index 000000000..f7fd8e01f --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/src/msg.rs @@ -0,0 +1,68 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Coin, Uint128}; +use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; + +#[cw_serde] +pub struct InstantiateMsg {} + +#[cw_serde] +pub enum ExecuteMsg { + Swap { + out_denom: String, + path: Option>, + minimum_receive: Option, + to: Option, + }, + SetPath { + offer_denom: String, + ask_denom: String, + path: Vec, + bidirectional: bool, + }, + RemovePath { + offer_denom: String, + ask_denom: String, + path: Vec, + bidirectional: bool, + }, +} + +#[cw_serde] +pub struct BestPathForPairResponse { + /// the path that will be used to perform the swap + pub path: Vec, + /// the amount of tokens that are expected to be received after the swap + pub return_amount: Uint128, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(Uint128)] + SimulateSwaps { + offer: Coin, + path: Vec, + }, + /// Returns all the current path for a given (offer_asset, ask_asset) pair. + #[returns(Vec>)] + PathsForPair { + offer_denom: String, + ask_denom: String, + }, + /// finds the best path for a given (offer_asset, ask_asset) pair. + /// if no path is found, returns None. + #[returns(Option)] + BestPathForPair { offer: Coin, ask_denom: String }, + + /// Returns all the assets from which there are paths to a given ask asset. + #[returns(Vec)] + SupportedOfferAssets { ask_denom: String }, + + /// Returns all the assets to which there are paths from a given offer + /// asset. + #[returns(Vec)] + SupportedAskAssets { offer_denom: String }, +} + +#[cw_serde] +pub struct MigrateMsg {} diff --git a/smart-contracts/contracts/dex-router-osmosis/src/state.rs b/smart-contracts/contracts/dex-router-osmosis/src/state.rs new file mode 100644 index 000000000..4cbd1f70f --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/src/state.rs @@ -0,0 +1,15 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; +use mars_owner::Owner; +use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; + +#[cw_serde] +pub struct RecipientInfo { + pub address: Addr, + pub denom: String, +} + +pub const PATHS: Map<(String, String), Vec>> = Map::new("paths"); +pub const RECIPIENT_INFO: Item = Item::new("recipient"); +pub const OWNER: Owner = Owner::new("owner"); diff --git a/smart-contracts/contracts/dex-router-osmosis/src/tests/mod.rs b/smart-contracts/contracts/dex-router-osmosis/src/tests/mod.rs new file mode 100644 index 000000000..0da79ae3f --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/src/tests/mod.rs @@ -0,0 +1,3 @@ +mod remove_path; +mod set_path; +mod swap; diff --git a/smart-contracts/contracts/dex-router-osmosis/src/tests/remove_path.rs b/smart-contracts/contracts/dex-router-osmosis/src/tests/remove_path.rs new file mode 100644 index 000000000..5f09f43a5 --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/src/tests/remove_path.rs @@ -0,0 +1,224 @@ +use crate::contract::{execute, instantiate}; +use crate::msg::{ExecuteMsg, InstantiateMsg}; +use crate::state::PATHS; +use crate::ContractError; +use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; +use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; + +#[test] +fn test_if_not_owner_then_remove_path_fails() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("owner", &[]); + let msg = InstantiateMsg {}; + assert!(instantiate(deps.as_mut(), env.clone(), info, msg).is_ok()); + + let info = mock_info("user", &[]); + + let msg = ExecuteMsg::RemovePath { + offer_denom: "from".to_string(), + ask_denom: "to".to_string(), + path: vec![], + bidirectional: true, + }; + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + assert_eq!( + err, + ContractError::Owner(mars_owner::OwnerError::NotOwner {}) + ); +} + +#[test] +fn test_if_path_is_empty_then_remove_path_fails() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("owner", &[]); + let msg = InstantiateMsg {}; + assert!(instantiate(deps.as_mut(), env.clone(), info.clone(), msg).is_ok()); + + let msg = ExecuteMsg::SetPath { + offer_denom: "from".to_string(), + ask_denom: "to".to_string(), + path: vec![], + bidirectional: true, + }; + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + assert_eq!(err, ContractError::EmptyPath {}); +} + +#[test] +fn test_remove_path_bidirectional_fails_if_reverse_path_does_not_exist() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("owner", &[]); + let msg = InstantiateMsg {}; + let offer_denom = "from".to_string(); + let ask_denom = "to".to_string(); + assert!(instantiate(deps.as_mut(), env.clone(), info.clone(), msg).is_ok()); + let key = (offer_denom.clone(), ask_denom.clone()); + let path = vec![ + SwapAmountInRoute { + pool_id: 0, + token_out_denom: "token0".to_string(), + }, + SwapAmountInRoute { + pool_id: 1, + token_out_denom: ask_denom.clone(), + }, + ]; + PATHS + .save(deps.as_mut().storage, key.clone(), &vec![path.clone()]) + .unwrap(); + + let msg = ExecuteMsg::RemovePath { + offer_denom: offer_denom.clone(), + ask_denom: ask_denom.clone(), + path: path.into_iter().map(|route| route.pool_id).collect(), + bidirectional: true, + }; + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + assert_eq!( + err, + ContractError::NoPathFound { + offer: ask_denom, + ask: offer_denom + } + ); +} + +#[test] +fn test_remove_path_bidirectional() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("owner", &[]); + let msg = InstantiateMsg {}; + let offer_denom = "from".to_string(); + let ask_denom = "to".to_string(); + assert!(instantiate(deps.as_mut(), env.clone(), info.clone(), msg).is_ok()); + let key = (offer_denom.clone(), ask_denom.clone()); + let key_rev = (ask_denom.clone(), offer_denom.clone()); + let path = vec![ + SwapAmountInRoute { + pool_id: 0, + token_out_denom: "token0".to_string(), + }, + SwapAmountInRoute { + pool_id: 1, + token_out_denom: ask_denom.clone(), + }, + ]; + let path_rev = vec![ + SwapAmountInRoute { + pool_id: 1, + token_out_denom: "token0".to_string(), + }, + SwapAmountInRoute { + pool_id: 0, + token_out_denom: offer_denom.clone(), + }, + ]; + PATHS + .save(deps.as_mut().storage, key.clone(), &vec![path.clone()]) + .unwrap(); + PATHS + .save( + deps.as_mut().storage, + key_rev.clone(), + &vec![path_rev.clone()], + ) + .unwrap(); + + let msg = ExecuteMsg::RemovePath { + offer_denom: offer_denom.clone(), + ask_denom: ask_denom.clone(), + path: path.into_iter().map(|route| route.pool_id).collect(), + bidirectional: true, + }; + assert!(execute(deps.as_mut(), env, info, msg).is_ok()); + assert_eq!(PATHS.may_load(deps.as_mut().storage, key).unwrap(), None); +} + +#[test] +fn test_remove_path() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("owner", &[]); + let msg = InstantiateMsg {}; + let offer_denom = "from".to_string(); + let ask_denom = "to".to_string(); + assert!(instantiate(deps.as_mut(), env.clone(), info.clone(), msg).is_ok()); + let key = (offer_denom.clone(), ask_denom.clone()); + let path = vec![ + SwapAmountInRoute { + pool_id: 0, + token_out_denom: "token0".to_string(), + }, + SwapAmountInRoute { + pool_id: 1, + token_out_denom: ask_denom.clone(), + }, + ]; + PATHS + .save(deps.as_mut().storage, key.clone(), &vec![path.clone()]) + .unwrap(); + + let msg = ExecuteMsg::RemovePath { + offer_denom, + ask_denom, + path: path.into_iter().map(|route| route.pool_id).collect(), + bidirectional: false, + }; + assert!(execute(deps.as_mut(), env, info, msg).is_ok()); + assert_eq!(PATHS.may_load(deps.as_mut().storage, key).unwrap(), None); +} + +#[test] +fn test_remove_one_of_two_paths() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("owner", &[]); + let msg = InstantiateMsg {}; + let offer_denom = "from".to_string(); + let ask_denom = "to".to_string(); + assert!(instantiate(deps.as_mut(), env.clone(), info.clone(), msg).is_ok()); + let key = (offer_denom.clone(), ask_denom.clone()); + let path1 = vec![ + SwapAmountInRoute { + pool_id: 0, + token_out_denom: "token0".to_string(), + }, + SwapAmountInRoute { + pool_id: 1, + token_out_denom: ask_denom.clone(), + }, + ]; + let path2 = vec![ + SwapAmountInRoute { + pool_id: 2, + token_out_denom: "token1".to_string(), + }, + SwapAmountInRoute { + pool_id: 3, + token_out_denom: ask_denom.clone(), + }, + ]; + PATHS + .save( + deps.as_mut().storage, + key.clone(), + &vec![path1.clone(), path2.clone()], + ) + .unwrap(); + + let msg = ExecuteMsg::RemovePath { + offer_denom, + ask_denom, + path: path1.into_iter().map(|route| route.pool_id).collect(), + bidirectional: false, + }; + assert!(execute(deps.as_mut(), env, info, msg).is_ok()); + assert_eq!( + PATHS.may_load(deps.as_mut().storage, key).unwrap(), + Some(vec![path2]) + ); +} diff --git a/smart-contracts/contracts/dex-router-osmosis/src/tests/set_path.rs b/smart-contracts/contracts/dex-router-osmosis/src/tests/set_path.rs new file mode 100644 index 000000000..5fd89bfc5 --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/src/tests/set_path.rs @@ -0,0 +1,45 @@ +use crate::contract::{execute, instantiate}; +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg}; +use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + +#[test] +fn test_if_not_owner_then_set_path_fails() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("owner", &[]); + let msg = InstantiateMsg {}; + assert!(instantiate(deps.as_mut(), env.clone(), info, msg).is_ok()); + + let info = mock_info("user", &[]); + + let msg = ExecuteMsg::SetPath { + offer_denom: "from".to_string(), + ask_denom: "to".to_string(), + path: vec![], + bidirectional: true, + }; + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + assert_eq!( + err, + ContractError::Owner(mars_owner::OwnerError::NotOwner {}) + ); +} + +#[test] +fn test_if_path_is_empty_then_set_path_fails() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("owner", &[]); + let msg = InstantiateMsg {}; + assert!(instantiate(deps.as_mut(), env.clone(), info.clone(), msg).is_ok()); + + let msg = ExecuteMsg::SetPath { + offer_denom: "from".to_string(), + ask_denom: "to".to_string(), + path: vec![], + bidirectional: true, + }; + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + assert_eq!(err, ContractError::EmptyPath {}); +} diff --git a/smart-contracts/contracts/dex-router-osmosis/src/tests/swap.rs b/smart-contracts/contracts/dex-router-osmosis/src/tests/swap.rs new file mode 100644 index 000000000..455eca777 --- /dev/null +++ b/smart-contracts/contracts/dex-router-osmosis/src/tests/swap.rs @@ -0,0 +1,46 @@ +use crate::contract::{execute, instantiate}; +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg}; +use cosmwasm_std::coin; +use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; +use quasar_types::error::FundsError; + +#[test] +fn test_swap_without_funds_throws() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("owner", &[]); + let msg = InstantiateMsg {}; + assert!(instantiate(deps.as_mut(), env.clone(), info, msg).is_ok()); + + let msg = ExecuteMsg::Swap { + path: None, + out_denom: "test".to_string(), + minimum_receive: None, + to: None, + }; + + let info = mock_info("user", &[]); + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + assert_eq!(err, ContractError::Funds(FundsError::InvalidAssets(1))) +} + +#[test] +fn test_swap_with_too_many_funds_throws() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("owner", &[]); + let msg = InstantiateMsg {}; + assert!(instantiate(deps.as_mut(), env.clone(), info, msg).is_ok()); + + let msg = ExecuteMsg::Swap { + path: None, + out_denom: "test".to_string(), + minimum_receive: None, + to: None, + }; + + let info = mock_info("user", &[coin(1000, "uosmo"), coin(1000, "uatom")]); + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + assert_eq!(err, ContractError::Funds(FundsError::InvalidAssets(1))) +} diff --git a/smart-contracts/contracts/merkle-incentives/Cargo.toml b/smart-contracts/contracts/merkle-incentives/Cargo.toml index afdc4dfa9..4c9d9badf 100644 --- a/smart-contracts/contracts/merkle-incentives/Cargo.toml +++ b/smart-contracts/contracts/merkle-incentives/Cargo.toml @@ -30,7 +30,7 @@ library = [] optimize = """docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/optimizer:0.15.0 + cosmwasm/optimizer:0.16.0 """ [dependencies] diff --git a/smart-contracts/contracts/range-middleware/Cargo.toml b/smart-contracts/contracts/range-middleware/Cargo.toml index 0662b6993..15c2a5c8e 100644 --- a/smart-contracts/contracts/range-middleware/Cargo.toml +++ b/smart-contracts/contracts/range-middleware/Cargo.toml @@ -30,7 +30,7 @@ library = [] optimize = """docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/optimizer:0.15.0 + cosmwasm/optimizer:0.16.0 """ [dependencies] @@ -41,9 +41,9 @@ cw2 ={ workspace = true } schemars = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } - +dex-router-osmosis = { workspace = true } cl-vault = {path = "../cl-vault", features = ["library"]} -cw-dex-router = {git = "https://github.com/quasar-finance/cw-dex-router", branch = "feat/deprecate-osmo-gamm", feature = ["library"]} +osmosis-std = "0.25.0" [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/smart-contracts/contracts/range-middleware/src/range/execute.rs b/smart-contracts/contracts/range-middleware/src/range/execute.rs index 82341e7f5..60e67f902 100644 --- a/smart-contracts/contracts/range-middleware/src/range/execute.rs +++ b/smart-contracts/contracts/range-middleware/src/range/execute.rs @@ -4,7 +4,7 @@ use cl_vault::{ }; use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_json_binary, Decimal, DepsMut, Env, MessageInfo, Response, WasmMsg}; -use cw_dex_router::operations::SwapOperationsListUnchecked; +use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; use crate::{ range::helpers::is_range_executor_admin, @@ -24,8 +24,7 @@ pub enum RangeExecuteMsg { max_slippage: Decimal, ratio_of_swappable_funds_to_use: Decimal, twap_window_seconds: u64, - recommended_swap_route: SwapOperationsListUnchecked, - force_swap_route: bool, + forced_swap_route: Option>, claim_after: Option, }, } @@ -35,8 +34,7 @@ pub struct RangeExecutionParams { pub max_slippage: Decimal, pub ratio_of_swappable_funds_to_use: Decimal, pub twap_window_seconds: u64, - pub recommended_swap_route: SwapOperationsListUnchecked, - pub force_swap_route: bool, + pub forced_swap_route: Option>, pub claim_after: Option, } @@ -55,8 +53,7 @@ pub fn execute_range_msg( max_slippage, ratio_of_swappable_funds_to_use, twap_window_seconds, - recommended_swap_route, - force_swap_route, + forced_swap_route, claim_after, } => execute_new_range( deps, @@ -67,8 +64,7 @@ pub fn execute_range_msg( max_slippage, ratio_of_swappable_funds_to_use, twap_window_seconds, - recommended_swap_route, - force_swap_route, + forced_swap_route, claim_after, }, ), @@ -152,8 +148,7 @@ pub fn execute_new_range( max_slippage: params.max_slippage, ratio_of_swappable_funds_to_use: params.ratio_of_swappable_funds_to_use, twap_window_seconds: params.twap_window_seconds, - force_swap_route: params.force_swap_route, - recommended_swap_route: Some(params.recommended_swap_route), + forced_swap_route: params.forced_swap_route, claim_after: params.claim_after, }), ))?, diff --git a/smart-contracts/contracts/vault-rewards/Cargo.toml b/smart-contracts/contracts/vault-rewards/Cargo.toml index df946d749..bb42d496e 100644 --- a/smart-contracts/contracts/vault-rewards/Cargo.toml +++ b/smart-contracts/contracts/vault-rewards/Cargo.toml @@ -36,7 +36,7 @@ library = [] optimize = """docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/optimizer:0.15.0 + cosmwasm/optimizer:0.16.0 """ [dependencies] diff --git a/smart-contracts/justfile b/smart-contracts/justfile index afa483920..7a93a1abb 100755 --- a/smart-contracts/justfile +++ b/smart-contracts/justfile @@ -20,7 +20,7 @@ test-tube-dev: workspace-optimize download-deps: mkdir -p artifacts target - wget https://github.com/quasar-finance/cw-dex-router/releases/latest/download/cw_dex_router.wasm -O artifacts/cw_dex_router.wasm + wget https://github.com/quasar-finance/cw-dex-router/releases/latest/download/cw_dex_router-osmosis.wasm -O artifacts/cw_dex_router-osmosis.wasm workspace-optimize: #!/bin/bash @@ -28,14 +28,14 @@ workspace-optimize: --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ --platform linux/arm64 \ - cosmwasm/workspace-optimizer-arm64:0.15.0; \ + cosmwasm/workspace-optimizer-arm64:0.16.0; \ elif [[ $(uname -m) == 'aarch64' ]]; then docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ --platform linux/arm64 \ - cosmwasm/workspace-optimizer-arm64:0.15.0; \ + cosmwasm/workspace-optimizer-arm64:0.16.0; \ elif [[ $(uname -m) == 'x86_64' ]]; then docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ --platform linux/amd64 \ - cosmwasm/workspace-optimizer:0.15.0; fi + cosmwasm/workspace-optimizer:0.16.0; fi diff --git a/smart-contracts/packages/quasar-types/src/error.rs b/smart-contracts/packages/quasar-types/src/error.rs index 99fb8dde3..d61070aa2 100644 --- a/smart-contracts/packages/quasar-types/src/error.rs +++ b/smart-contracts/packages/quasar-types/src/error.rs @@ -1,4 +1,6 @@ -use cosmwasm_std::{CheckedFromRatioError, DivideByZeroError, IbcOrder, OverflowError, StdError}; +use cosmwasm_std::{ + CheckedFromRatioError, Coin, DivideByZeroError, IbcOrder, OverflowError, StdError, +}; use prost::DecodeError; use thiserror::Error; @@ -59,3 +61,31 @@ pub enum Error { #[error("{0}")] CheckedFromRatioError(#[from] CheckedFromRatioError), } + +#[derive(Error, Debug, PartialEq)] +pub enum FundsError { + #[error("Only {0} deposit asset(s) supported.")] + InvalidAssets(usize), + + #[error("Wrong denom, expected {0}.")] + WrongDenom(String), +} + +pub fn assert_fund_length(length: usize, expected_length: usize) -> Result<(), FundsError> { + if length != expected_length { + return Err(FundsError::InvalidAssets(expected_length)); + } + Ok(()) +} + +pub fn assert_denom(denom: &str, expected_denom: &str) -> Result<(), FundsError> { + if denom != expected_denom { + return Err(FundsError::WrongDenom(expected_denom.into())); + } + Ok(()) +} + +pub fn assert_funds_single_token(funds: &[Coin], expected_denom: &str) -> Result<(), FundsError> { + assert_fund_length(funds.len(), 1)?; + assert_denom(&funds[0].denom, expected_denom) +}