diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 95d032b68..1a3fc4f0a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -92,6 +92,7 @@ jobs: smart-contracts/artifacts/cl_vault.wasm smart-contracts/artifacts/merkle_incentives.wasm smart-contracts/artifacts/dex_router_osmosis.wasm + smart-contracts/artifacts/lst_adapter_osmosis.wasm test-test-tube: runs-on: ubuntu-latest needs: build-rust diff --git a/smart-contracts/Cargo.lock b/smart-contracts/Cargo.lock index dc3ac6c9a..d0bdec7f8 100644 --- a/smart-contracts/Cargo.lock +++ b/smart-contracts/Cargo.lock @@ -244,6 +244,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "abstract-cw-orch-polytone" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9607f7cf2a40564906b0ed092c750843dbf66a5ff298a40df9558823cc088f32" +dependencies = [ + "abstract-polytone-note 2.0.0", + "abstract-polytone-proxy", + "abstract-polytone-voice", + "cosmwasm-std", + "cw-orch 0.22.2", +] + [[package]] name = "abstract-cw-plus-interface" version = "2.0.2" @@ -542,7 +555,7 @@ checksum = "3576a483bf0668624335eb7674da419bec75db1dda2ecd9b14da7ca47d8bdd8c" dependencies = [ "abstract-macros 0.22.1 (registry+https://github.com/rust-lang/crates.io-index)", "abstract-polytone", - "abstract-polytone-note", + "abstract-polytone-note 1.0.5", "abstract-sdk 0.22.2", "abstract-std 0.22.2", "cosmwasm-std", @@ -563,7 +576,7 @@ source = "git+https://github.com/AbstractSDK/abstract.git?tag=v0.22.1#b376308501 dependencies = [ "abstract-macros 0.22.1 (git+https://github.com/AbstractSDK/abstract.git?tag=v0.22.1)", "abstract-polytone", - "abstract-polytone-note", + "abstract-polytone-note 1.0.5", "abstract-sdk 0.22.1", "abstract-std 0.22.1", "cosmwasm-std", @@ -874,6 +887,56 @@ dependencies = [ "thiserror", ] +[[package]] +name = "abstract-polytone-note" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b8e658ed796cc9c0fe3b837ae430c168a53aa7852db78fdaef07c99542f77e" +dependencies = [ + "abstract-polytone", + "cosmwasm-schema 1.5.4", + "cosmwasm-std", + "cw-orch 0.22.2", + "cw-storage-plus 1.2.0", + "cw-utils", + "cw2 1.1.2", + "thiserror", +] + +[[package]] +name = "abstract-polytone-proxy" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ce4a8ce93e50ab42e952d6848c45000cf0efe41f39d37f8fd55205086dc845" +dependencies = [ + "abstract-polytone", + "cosmwasm-schema 1.5.4", + "cosmwasm-std", + "cw-orch 0.22.2", + "cw-storage-plus 1.2.0", + "cw-utils", + "cw2 1.1.2", + "thiserror", +] + +[[package]] +name = "abstract-polytone-voice" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf7140953864e14b73d572921699df09f2ad75cb86136abce6dc4a3d9ab8b9" +dependencies = [ + "abstract-polytone", + "abstract-polytone-proxy", + "cosmwasm-schema 1.5.4", + "cosmwasm-std", + "cw-orch 0.22.2", + "cw-storage-plus 1.2.0", + "cw-utils", + "cw2 1.1.2", + "sha2 0.10.8", + "thiserror", +] + [[package]] name = "abstract-proxy" version = "0.22.1" @@ -1362,6 +1425,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -1370,9 +1439,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -1394,17 +1463,17 @@ checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "bindgen" -version = "0.69.2" +version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c69fae65a523209d34240b60abe0c42d33d1045d445c0839d8a4894a736e2d" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ "bitflags 2.4.2", "cexpr", "clang-sys", + "itertools 0.12.1", "lazy_static", "lazycell", "log", - "peeking_take_while", "prettyplease", "proc-macro2", "quote", @@ -1567,9 +1636,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytecheck" @@ -1677,9 +1746,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -1688,9 +1757,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.7" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" dependencies = [ "clap_builder", "clap_derive", @@ -1698,9 +1767,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" dependencies = [ "anstream", "anstyle", @@ -1710,9 +1779,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.5" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck", "proc-macro2", @@ -1792,7 +1861,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.3", "tendermint-proto 0.34.0", - "tonic", + "tonic 0.10.2", ] [[package]] @@ -1822,7 +1891,7 @@ dependencies = [ "serde_json", "signature", "subtle-encoding", - "tendermint", + "tendermint 0.34.0", "tendermint-rpc", "thiserror", "tokio", @@ -2215,7 +2284,7 @@ dependencies = [ "sha2 0.10.8", "thiserror", "tokio", - "tonic", + "tonic 0.10.2", ] [[package]] @@ -2242,6 +2311,62 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "cw-orch-interchain" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fcf7defbb524e4db4fafd920732de1a7a9ef467b28116223b1e153971e5955" +dependencies = [ + "cosmwasm-std", + "cw-orch-interchain-core", + "cw-orch-interchain-mock", + "cw1", + "cw1-whitelist", + "speculoos", +] + +[[package]] +name = "cw-orch-interchain-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac27dd32926b7f529ed0905e7d5c610126d5eddd8381afd5918831b21c900d" +dependencies = [ + "base64 0.21.7", + "cosmwasm-schema 1.5.4", + "cosmwasm-std", + "cw-orch-core", + "cw-orch-mock", + "futures", + "ibc-relayer-types", + "log", + "polytone", + "prost 0.12.6", + "serde_json", + "thiserror", + "tokio", + "tonic 0.10.2", +] + +[[package]] +name = "cw-orch-interchain-mock" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2042d10d65038139d8c2f0a1010ae050b67a43da9b6ee4f4fc2936b2d998b36" +dependencies = [ + "anyhow", + "cosmrs", + "cosmwasm-std", + "cw-orch-core", + "cw-orch-interchain-core", + "cw-orch-mock", + "cw-utils", + "ibc-relayer-types", + "log", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cw-orch-mock" version = "0.22.4" @@ -2408,6 +2533,35 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw1" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1605722190afd93bfea6384b88224d1cfe50ebf70d2e10641535da79fa70e83" +dependencies = [ + "cosmwasm-schema 1.5.4", + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw1-whitelist" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bb3e9dc87f4ff26547f4e27e0ba3c82034372f21b2f55527fb52b542637d8d" +dependencies = [ + "cosmwasm-schema 1.5.4", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils", + "cw1", + "cw2 1.1.2", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw2" version = "0.10.3" @@ -2786,9 +2940,9 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -2822,11 +2976,20 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "erased-serde" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" +dependencies = [ + "serde", +] + [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2844,9 +3007,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "ff" @@ -2874,6 +3037,15 @@ dependencies = [ "libc", ] +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "static_assertions", +] + [[package]] name = "flate2" version = "1.0.30" @@ -2890,6 +3062,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" dependencies = [ + "anyhow", "eyre", "paste", ] @@ -2959,6 +3132,7 @@ checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -2981,12 +3155,34 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "futures-sink" version = "0.3.30" @@ -3005,11 +3201,16 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -3114,9 +3315,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.4" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -3289,6 +3490,83 @@ dependencies = [ "cc", ] +[[package]] +name = "ibc-proto" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c352715b36685c2543556a77091fb16af5d26257d5ce9c28e6756c1ccd71aa" +dependencies = [ + "base64 0.21.7", + "bytes", + "flex-error", + "ics23", + "prost 0.11.9", + "serde", + "subtle-encoding", + "tendermint-proto 0.32.2", + "tonic 0.9.2", +] + +[[package]] +name = "ibc-relayer-types" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fa9269c050d20b36a9e61955a5526345df1508f396f7f3a9acb4c03cdb572f3" +dependencies = [ + "bytes", + "derive_more", + "dyn-clone", + "erased-serde", + "flex-error", + "ibc-proto", + "ics23", + "itertools 0.10.5", + "num-rational", + "primitive-types", + "prost 0.11.9", + "regex", + "serde", + "serde_derive", + "serde_json", + "subtle-encoding", + "tendermint 0.32.2", + "tendermint-light-client-verifier", + "tendermint-proto 0.32.2", + "time", + "uint", +] + +[[package]] +name = "ica-oracle" +version = "1.0.0" +source = "git+https://github.com/Stride-Labs/ica-oracle?tag=v1.0.0#2fdf76f3ba4fad6a20a6d10d77c0511f2439b6c3" +dependencies = [ + "cosmwasm-schema 1.5.4", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "hex", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "ics23" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442d4bab37956e76f739c864f246c825d87c0bb7f9afa65660c57833c91bf6d4" +dependencies = [ + "anyhow", + "bytes", + "hex", + "informalsystems-pbjson", + "prost 0.11.9", + "ripemd", + "serde", + "sha2 0.10.8", + "sha3", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -3305,6 +3583,15 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + [[package]] name = "indenter" version = "0.3.3" @@ -3331,6 +3618,16 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "informalsystems-pbjson" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4eecd90f87bea412eac91c6ef94f6b1e390128290898cbe14f2b926787ae1fb" +dependencies = [ + "base64 0.13.1", + "serde", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -3378,9 +3675,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -3399,11 +3696,20 @@ dependencies = [ "signature", ] +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" @@ -3419,12 +3725,12 @@ checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.0", ] [[package]] @@ -3445,9 +3751,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -3465,6 +3771,33 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lst-adapter-osmosis" +version = "0.0.1" +dependencies = [ + "abstract-app", + "abstract-client", + "abstract-cw-orch-polytone", + "abstract-interface 0.22.5", + "abstract-polytone", + "abstract-sdk 0.22.2", + "abstract-std 0.22.2", + "const_format", + "cosmwasm-schema 1.5.4", + "cosmwasm-std", + "cw-orch 0.23.0", + "cw-orch-interchain", + "cw-storage-plus 1.2.0", + "ibc-relayer-types", + "ica-oracle", + "mars-owner", + "osmosis-std", + "prost 0.12.6", + "quasar-types", + "serde_json", + "thiserror", +] + [[package]] name = "lst-dex-adapter-osmosis" version = "0.0.1" @@ -3482,6 +3815,7 @@ dependencies = [ "cw-storage-plus 1.2.0", "dotenv", "env_logger", + "lst-adapter-osmosis", "quasar-types", "thiserror", "wyndex-bundle", @@ -3516,7 +3850,7 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" name = "merkle-incentives" version = "0.1.0" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "cosmwasm-schema 1.5.4", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -3860,7 +4194,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.2", + "redox_syscall", "smallvec", "windows-targets 0.52.0", ] @@ -3880,12 +4214,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "peg" version = "0.7.0" @@ -3967,6 +4295,18 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "polytone" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f16d20da9144fdf0658e785fc9108b86cecee517335ff531745029dd56088" +dependencies = [ + "cosmwasm-schema 1.5.4", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "thiserror", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -3975,14 +4315,25 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" dependencies = [ "proc-macro2", "syn 2.0.48", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "3.1.0" @@ -4026,9 +4377,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", @@ -4154,6 +4505,7 @@ dependencies = [ "schemars", "serde", "serde-json-wasm 1.0.1", + "serde_json", "serde_test", "thiserror", ] @@ -4241,15 +4593,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.2" @@ -4310,9 +4653,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64 0.21.7", "bytes", @@ -4339,6 +4682,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", @@ -4496,9 +4840,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.30" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ "bitflags 2.4.2", "errno", @@ -4837,6 +5181,16 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + [[package]] name = "shlex" version = "1.3.0" @@ -5026,17 +5380,45 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.4.1", "rustix", "windows-sys 0.52.0", ] +[[package]] +name = "tendermint" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f0a7d05cf78524782337f8edd55cbc578d159a16ad4affe2135c92f7dbac7f0" +dependencies = [ + "bytes", + "digest 0.10.7", + "ed25519", + "ed25519-consensus", + "flex-error", + "futures", + "num-traits", + "once_cell", + "prost 0.11.9", + "prost-types 0.11.9", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.10.8", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.32.2", + "time", + "zeroize", +] + [[package]] name = "tendermint" version = "0.34.0" @@ -5077,11 +5459,42 @@ dependencies = [ "flex-error", "serde", "serde_json", - "tendermint", + "tendermint 0.34.0", "toml", "url", ] +[[package]] +name = "tendermint-light-client-verifier" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9875dce5c1b08201152eb0860f8fb1dce96c53e37532c310ffc4956d20f90def" +dependencies = [ + "derive_more", + "flex-error", + "serde", + "tendermint 0.32.2", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cec054567d16d85e8c3f6a3139963d1a66d9d3051ed545d31562550e9bcc3d" +dependencies = [ + "bytes", + "flex-error", + "num-derive 0.3.3", + "num-traits", + "prost 0.11.9", + "prost-types 0.11.9", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + [[package]] name = "tendermint-proto" version = "0.34.0" @@ -5138,7 +5551,7 @@ dependencies = [ "serde_json", "subtle", "subtle-encoding", - "tendermint", + "tendermint 0.34.0", "tendermint-config", "tendermint-proto 0.34.0", "thiserror", @@ -5345,6 +5758,34 @@ dependencies = [ "winnow", ] +[[package]] +name = "tonic" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +dependencies = [ + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost 0.11.9", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tonic" version = "0.10.2" @@ -5483,9 +5924,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -5510,9 +5951,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -5585,9 +6026,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -5595,9 +6036,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -5610,9 +6051,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -5622,9 +6063,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5632,9 +6073,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -5645,9 +6086,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasmswap" @@ -5668,9 +6109,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/smart-contracts/Cargo.toml b/smart-contracts/Cargo.toml index 81ee449a7..270f37c13 100644 --- a/smart-contracts/Cargo.toml +++ b/smart-contracts/Cargo.toml @@ -16,6 +16,7 @@ members = [ "contracts/range-middleware", "contracts/dex-router-osmosis", "contracts/lst-dex-adapter-osmosis", + "contracts/lst-adapter-osmosis", ] [workspace.dependencies] @@ -35,6 +36,8 @@ apollo-cw-asset = "0.1.2" mars-owner = "2.0.0" quasar-types = { path = "packages/quasar-types" } dex-router-osmosis = { path = "contracts/dex-router-osmosis", features = ["library"] } +lst-dex-adapter-osmosis = { path = "contracts/lst-dex-adapter-osmosis", features = ["library"] } +lst-adapter-osmosis = { path = "contracts/lst-adapter-osmosis", features = ["library"] } # SDK cosmos-sdk-proto = {version = "0.21.1", default-features = false} @@ -62,11 +65,15 @@ abstract-testing = { version = "0.22.1" } abstract-adapter = { version = "0.22.2" } abstract-interface = { version = "0.22.0" } abstract-client = { version = "0.22.0" } +abstract-polytone = { version = "1.0.5" } +abstract-cw-orch-polytone = { version = "2.0.0" } cw-orch = { version = "0.23.0" } +cw-orch-interchain = { version = "0.1.0" } clap = { version = "4.3.7" } const_format = "0.2.32" dotenv = "0.15.0" env_logger = "0.11.3" +ibc-relayer-types = "0.25" # Testing cw-multi-test = "1.2.0" diff --git a/smart-contracts/contracts/lst-adapter-osmosis/.github/workflows/Basic.yml b/smart-contracts/contracts/lst-adapter-osmosis/.github/workflows/Basic.yml new file mode 100644 index 000000000..6ece421c0 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/.github/workflows/Basic.yml @@ -0,0 +1,73 @@ +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/lst-adapter-osmosis/Cargo.toml b/smart-contracts/contracts/lst-adapter-osmosis/Cargo.toml new file mode 100644 index 000000000..6b94d49c5 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/Cargo.toml @@ -0,0 +1,55 @@ +[package] +authors = ["Quasar"] +edition = "2021" +name = "lst-adapter-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 adapter contract for unbonding stride LSTs on osmosis" +keywords = ["cosmwasm", "lst", "osmosis"] + +exclude = [ + "lst_adapter_osmosis.wasm", + "hash.txt", +] + +[[bin]] +name = "schema" +required-features = ["schema"] + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["export"] +export = [] +library = [] +schema = ["abstract-app/schema"] + +[dependencies] +cosmwasm-std = { workspace = true, features = ["stargate"] } +cosmwasm-schema = { workspace = true } +cw-storage-plus = { workspace = true } +thiserror = { workspace = true } +mars-owner = { workspace = true } +osmosis-std = { workspace = true } +quasar-types = { workspace = true } +abstract-app = { workspace = true } +abstract-std = { workspace = true } +abstract-sdk = { workspace = true } +cw-orch = { workspace = true } +const_format = { workspace = true } +prost = { workspace = true } +serde_json = { workspace = true } +ica-oracle = { git = "https://github.com/Stride-Labs/ica-oracle", tag="v1.0.0" } + +[dev-dependencies] +abstract-client = { workspace = true } +cw-orch-interchain = { workspace = true } +abstract-interface = { workspace = true } +abstract-polytone = { workspace = true } +abstract-cw-orch-polytone = { workspace = true } +ibc-relayer-types = { workspace = true } \ No newline at end of file diff --git a/smart-contracts/contracts/lst-adapter-osmosis/README.md b/smart-contracts/contracts/lst-adapter-osmosis/README.md new file mode 100644 index 000000000..016a1b5d5 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/README.md @@ -0,0 +1,37 @@ +# Lst adapter between osmosis and stride + +There are three parties that can perform action on this vault: + * Owner: can change the configuration + * Vault: can trigger unbonding of LSTs and claim unbonded tokens + * Observer: can confirm the initiation of an unbonding process on stride as well as the receival of unbonded tokens on osmosis + +# How it works + +We can't query outstanding redemptions on stride on-chain. Thus, we need to monitor them off-chain. Similarly, when we receive the underlying token, these just occur in the lst adapter at some point in time (stride IBC-transfers them back when they unlock). +Therefore, we need an off-chain observer that informs the lst-adapter when + * an unbonding process, that was initiated from osmosis through an IBC-transfer and stride's autopilot, is actually started on stride (param: the exact amount of tokens that we will receive when unbonding finishes) + * when an unbonding finishes (param: unbonding start timestamp) + +### Unbond +In practice this means the following process for a fully confirmed unbonding: +* trigger unbond (vault): `LstAdapterExecuteMsg::Unbond` + * this either triggers IBC-transfer and unbonding process via autopilot + * or, if an unbonding process is yet unconfirmed, keeps the LST tokens in the lst-adapter until the next unbonding process is confirmed and the next unbond is executed +* confirm unbond as soon as it gets observed on stride (observer): `LstAdapterExecutMsg::ConfirmUnbond` + +The possibly delay in unbonding until the last unbond gets confirmed can be loosened. Until this is fully tested and stable, I prefer to keep things simple at the risk of slightly delaying the unbonding process. This is not nice, but not a big issue as the delays should be negligible wrt. the unbonding period. + +One strategy for loosening this restriction is to take into account the unbond amount and the max change of the redemption rate until an IBC-timeout occurs. This would allow define a narrow range in which the confirmation must fall, so IBC-transactions for unbondings that have non-overlapping ranges could be processed asynchronously. +### Claim +Assuming no claiming of "donations", the claim process looks as follows: +* confirm reception of tokens from stride (observer): `LstAdapterExecuteMsg::ConfirmUnbondFinished` +* claim (vault): `LstAdapterExecuteMsg::Claim` + +# Internals: +In order to track the tokens that correspond to the lst-adapter, two internal variables are used + * TOTAL_BALANCE: tracks the total balance in terms of the underlying token + * REDEEMED_BALANCE: tracks the amount of unclaimed and redeemed tokens + +These variables allow us to track funds, without interference through "donations". In order to keep the complexity of the interference of "donations" low, these are added to the lst-vault balance at two places: + * "donations" in terms of the underlying token are accounted for during the claim process, (i.e. the calling contract should call Claim in a SubMsg in order to correctly parse the received amount either from events or through a query) + * "donations" in terms of the lst-token are accounted for instantly diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/execute_msg.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/execute_msg.json new file mode 100644 index 000000000..6330fdbe2 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/execute_msg.json @@ -0,0 +1,1346 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "Wrapper around all possible messages that can be sent to the module.", + "oneOf": [ + { + "description": "A configuration message, defined by the base.", + "type": "object", + "required": [ + "base" + ], + "properties": { + "base": { + "$ref": "#/definitions/BaseExecuteMsg" + } + }, + "additionalProperties": false + }, + { + "description": "An app request defined by a base consumer.", + "type": "object", + "required": [ + "module" + ], + "properties": { + "module": { + "$ref": "#/definitions/LstAdapterExecuteMsg" + } + }, + "additionalProperties": false + }, + { + "description": "IbcReceive to process IBC callbacks In order to trust this, the apps and adapters verify this comes from the ibc-client contract.", + "type": "object", + "required": [ + "ibc_callback" + ], + "properties": { + "ibc_callback": { + "$ref": "#/definitions/IbcResponseMsg" + } + }, + "additionalProperties": false + }, + { + "description": "ModuleIbc endpoint to receive messages from modules on other chains In order to trust this, the apps and adapters verify this comes from the ibc-host contract. They should also trust the sending chain", + "type": "object", + "required": [ + "module_ibc" + ], + "properties": { + "module_ibc": { + "$ref": "#/definitions/ModuleIbcMsg" + } + }, + "additionalProperties": false + }, + { + "description": "Receive endpoint for CW20 / external service integrations", + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Attribute": { + "description": "An key value pair that is used in the context of event attributes in logs", + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "BankQuery": { + "oneOf": [ + { + "description": "This calls into the native bank module for querying the total supply of one denomination. It does the same as the SupplyOf call in Cosmos SDK's RPC API. Return value is of type SupplyResponse.", + "type": "object", + "required": [ + "supply" + ], + "properties": { + "supply": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This calls into the native bank module for one denomination Return value is BalanceResponse", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object", + "required": [ + "address", + "denom" + ], + "properties": { + "address": { + "type": "string" + }, + "denom": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This calls into the native bank module for all denominations. Note that this may be much more expensive than Balance and should be avoided if possible. Return value is AllBalanceResponse.", + "type": "object", + "required": [ + "all_balances" + ], + "properties": { + "all_balances": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This calls into the native bank module for querying metadata for a specific bank token. Return value is DenomMetadataResponse", + "type": "object", + "required": [ + "denom_metadata" + ], + "properties": { + "denom_metadata": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This calls into the native bank module for querying metadata for all bank tokens that have a metadata entry. Return value is AllDenomMetadataResponse", + "type": "object", + "required": [ + "all_denom_metadata" + ], + "properties": { + "all_denom_metadata": { + "type": "object", + "properties": { + "pagination": { + "anyOf": [ + { + "$ref": "#/definitions/PageRequest" + }, + { + "type": "null" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "BaseExecuteMsg": { + "oneOf": [ + { + "description": "Updates the base config", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "ans_host_address": { + "type": [ + "string", + "null" + ] + }, + "version_control_address": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "CallbackResult": { + "oneOf": [ + { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "object", + "required": [ + "query", + "result" + ], + "properties": { + "query": { + "$ref": "#/definitions/QueryRequest_for_Empty" + }, + "result": { + "$ref": "#/definitions/Result_of_Array_of_Binary_or_ErrorResponse" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "initiator_msg", + "result" + ], + "properties": { + "initiator_msg": { + "$ref": "#/definitions/Binary" + }, + "result": { + "$ref": "#/definitions/Result_of_ExecutionResponse_or_String" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "An error occured that could not be recovered from. The only known way that this can occur is message handling running out of gas, in which case the error will be `codespace: sdk, code: 11`.\n\nThis error is not named becuase it could also occur due to a panic or unhandled error during message processing. We don't expect this to happen and have carefully written the code to avoid it.", + "type": "object", + "required": [ + "fatal_error" + ], + "properties": { + "fatal_error": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "ChainName": { + "description": "The name of a chain, aka the chain-id without the post-fix number. ex. `cosmoshub-4` -> `cosmoshub`, `juno-1` -> `juno`", + "type": "string" + }, + "Denoms": { + "type": "object", + "required": [ + "lst", + "underlying" + ], + "properties": { + "lst": { + "type": "string" + }, + "underlying": { + "type": "string" + } + }, + "additionalProperties": false + }, + "DistributionQuery": { + "oneOf": [ + { + "description": "See ", + "type": "object", + "required": [ + "delegator_withdraw_address" + ], + "properties": { + "delegator_withdraw_address": { + "type": "object", + "required": [ + "delegator_address" + ], + "properties": { + "delegator_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "See ", + "type": "object", + "required": [ + "delegation_rewards" + ], + "properties": { + "delegation_rewards": { + "type": "object", + "required": [ + "delegator_address", + "validator_address" + ], + "properties": { + "delegator_address": { + "type": "string" + }, + "validator_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "See ", + "type": "object", + "required": [ + "delegation_total_rewards" + ], + "properties": { + "delegation_total_rewards": { + "type": "object", + "required": [ + "delegator_address" + ], + "properties": { + "delegator_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "See ", + "type": "object", + "required": [ + "delegator_validators" + ], + "properties": { + "delegator_validators": { + "type": "object", + "required": [ + "delegator_address" + ], + "properties": { + "delegator_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "ErrorResponse": { + "type": "object", + "required": [ + "error", + "message_index" + ], + "properties": { + "error": { + "description": "The error that occured executing the message.", + "type": "string" + }, + "message_index": { + "description": "The index of the first message who's execution failed.", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + } + }, + "additionalProperties": false + }, + "Event": { + "description": "A full [*Cosmos SDK* event].\n\nThis version uses string attributes (similar to [*Cosmos SDK* StringEvent]), which then get magically converted to bytes for Tendermint somewhere between the Rust-Go interface, JSON deserialization and the `NewEvent` call in Cosmos SDK.\n\n[*Cosmos SDK* event]: https://docs.cosmos.network/main/learn/advanced/events [*Cosmos SDK* StringEvent]: https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/base/abci/v1beta1/abci.proto#L56-L70", + "type": "object", + "required": [ + "attributes", + "type" + ], + "properties": { + "attributes": { + "description": "The attributes to be included in the event.\n\nYou can learn more about these from [*Cosmos SDK* docs].\n\n[*Cosmos SDK* docs]: https://docs.cosmos.network/main/learn/advanced/events", + "type": "array", + "items": { + "$ref": "#/definitions/Attribute" + } + }, + "type": { + "description": "The event type. This is renamed to \"ty\" because \"type\" is reserved in Rust. This sucks, we know.", + "type": "string" + } + } + }, + "ExecutionResponse": { + "type": "object", + "required": [ + "executed_by", + "result" + ], + "properties": { + "executed_by": { + "description": "The address on the remote chain that executed the messages.", + "type": "string" + }, + "result": { + "description": "Index `i` corresponds to the result of executing the `i`th message.", + "type": "array", + "items": { + "$ref": "#/definitions/SubMsgResponse" + } + } + }, + "additionalProperties": false + }, + "IbcQuery": { + "description": "These are queries to the various IBC modules to see the state of the contract's IBC connection. These will return errors if the contract is not \"ibc enabled\"", + "oneOf": [ + { + "description": "Gets the Port ID the current contract is bound to.\n\nReturns a `PortIdResponse`.", + "type": "object", + "required": [ + "port_id" + ], + "properties": { + "port_id": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Lists all channels that are bound to a given port. If `port_id` is omitted, this list all channels bound to the contract's port.\n\nReturns a `ListChannelsResponse`.", + "type": "object", + "required": [ + "list_channels" + ], + "properties": { + "list_channels": { + "type": "object", + "properties": { + "port_id": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Lists all information for a (portID, channelID) pair. If port_id is omitted, it will default to the contract's own channel. (To save a PortId{} call)\n\nReturns a `ChannelResponse`.", + "type": "object", + "required": [ + "channel" + ], + "properties": { + "channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "port_id": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcResponseMsg": { + "description": "IbcResponseMsg should be de/serialized under `IbcCallback()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "id", + "result" + ], + "properties": { + "id": { + "description": "The ID chosen by the caller in the `callback_info.id`", + "type": "string" + }, + "msg": { + "description": "The msg sent with the callback request. This is usually used to provide information to the ibc callback function for context", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "result": { + "$ref": "#/definitions/CallbackResult" + } + }, + "additionalProperties": false + }, + "LstAdapterExecuteMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "unbond" + ], + "properties": { + "unbond": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "confirm_unbond" + ], + "properties": { + "confirm_unbond": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "confirm_unbond_finished" + ], + "properties": { + "confirm_unbond_finished": { + "type": "object", + "required": [ + "unbond_start_time" + ], + "properties": { + "unbond_start_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_ibc_config" + ], + "properties": { + "update_ibc_config": { + "type": "object", + "required": [ + "channel", + "remote_chain" + ], + "properties": { + "block_offset": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "channel": { + "type": "string" + }, + "remote_chain": { + "type": "string" + }, + "revision": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "timeout_secs": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update" + ], + "properties": { + "update": { + "type": "object", + "properties": { + "denoms": { + "anyOf": [ + { + "$ref": "#/definitions/Denoms" + }, + { + "type": "null" + } + ] + }, + "observer": { + "type": [ + "string", + "null" + ] + }, + "stride_oracle": { + "type": [ + "string", + "null" + ] + }, + "unbond_period_secs": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "vault": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_owner" + ], + "properties": { + "update_owner": { + "$ref": "#/definitions/OwnerUpdate" + } + }, + "additionalProperties": false + } + ] + }, + "ModuleIbcMsg": { + "type": "object", + "required": [ + "client_chain", + "msg", + "source_module" + ], + "properties": { + "client_chain": { + "description": "Remote chain identification", + "allOf": [ + { + "$ref": "#/definitions/ChainName" + } + ] + }, + "msg": { + "description": "The message sent by the module", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "source_module": { + "description": "Information about the module that called ibc action on this module", + "allOf": [ + { + "$ref": "#/definitions/ModuleInfo" + } + ] + } + }, + "additionalProperties": false + }, + "ModuleInfo": { + "description": "Stores the namespace, name, and version of an Abstract module.", + "type": "object", + "required": [ + "name", + "namespace", + "version" + ], + "properties": { + "name": { + "description": "Name of the contract", + "type": "string" + }, + "namespace": { + "description": "Namespace of the module", + "allOf": [ + { + "$ref": "#/definitions/Namespace" + } + ] + }, + "version": { + "description": "Version of the module", + "allOf": [ + { + "$ref": "#/definitions/ModuleVersion" + } + ] + } + }, + "additionalProperties": false + }, + "ModuleVersion": { + "oneOf": [ + { + "type": "string", + "enum": [ + "latest" + ] + }, + { + "type": "object", + "required": [ + "version" + ], + "properties": { + "version": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Namespace": { + "description": "Represents an Abstract namespace for modules", + "type": "string" + }, + "OwnerUpdate": { + "oneOf": [ + { + "description": "Proposes a new owner to take role. Only current owner can execute.", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "proposed" + ], + "properties": { + "proposed": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Clears the currently proposed owner. Only current owner can execute.", + "type": "string", + "enum": [ + "clear_proposed" + ] + }, + { + "description": "Promotes the proposed owner to be the current one. Only the proposed owner can execute.", + "type": "string", + "enum": [ + "accept_proposed" + ] + }, + { + "description": "Throws away the keys to the Owner role forever. Once done, no owner can ever be set later.", + "type": "string", + "enum": [ + "abolish_owner_role" + ] + } + ] + }, + "PageRequest": { + "description": "Simplified version of the PageRequest type for pagination from the cosmos-sdk", + "type": "object", + "required": [ + "limit", + "reverse" + ], + "properties": { + "key": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "limit": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "reverse": { + "type": "boolean" + } + } + }, + "QueryRequest_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankQuery" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingQuery" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionQuery" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate query is encoded the same way as abci_query, with path and protobuf encoded request data. The format is defined in [ADR-21](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-021-protobuf-query-encoding.md). The response is protobuf encoded data directly without a JSON response wrapper. The caller is responsible for compiling the proper protobuf definitions for both requests and responses.", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "data", + "path" + ], + "properties": { + "data": { + "description": "this is the expected protobuf message type (not any), binary encoded", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "path": { + "description": "this is the fully qualified service path used for routing, eg. custom/cosmos_sdk.x.bank.v1.Query/QueryBalance", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcQuery" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmQuery" + } + }, + "additionalProperties": false + } + ] + }, + "Result_of_Array_of_Binary_or_ErrorResponse": { + "oneOf": [ + { + "type": "object", + "required": [ + "Ok" + ], + "properties": { + "Ok": { + "type": "array", + "items": { + "$ref": "#/definitions/Binary" + } + } + } + }, + { + "type": "object", + "required": [ + "Err" + ], + "properties": { + "Err": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + ] + }, + "Result_of_ExecutionResponse_or_String": { + "oneOf": [ + { + "type": "object", + "required": [ + "Ok" + ], + "properties": { + "Ok": { + "$ref": "#/definitions/ExecutionResponse" + } + } + }, + { + "type": "object", + "required": [ + "Err" + ], + "properties": { + "Err": { + "type": "string" + } + } + } + ] + }, + "StakingQuery": { + "oneOf": [ + { + "description": "Returns the denomination that can be bonded (if there are multiple native tokens on the chain)", + "type": "object", + "required": [ + "bonded_denom" + ], + "properties": { + "bonded_denom": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "AllDelegations will return all delegations by the delegator", + "type": "object", + "required": [ + "all_delegations" + ], + "properties": { + "all_delegations": { + "type": "object", + "required": [ + "delegator" + ], + "properties": { + "delegator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Delegation will return more detailed info on a particular delegation, defined by delegator/validator pair", + "type": "object", + "required": [ + "delegation" + ], + "properties": { + "delegation": { + "type": "object", + "required": [ + "delegator", + "validator" + ], + "properties": { + "delegator": { + "type": "string" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns all validators in the currently active validator set.\n\nThe query response type is `AllValidatorsResponse`.", + "type": "object", + "required": [ + "all_validators" + ], + "properties": { + "all_validators": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Returns the validator at the given address. Returns None if the validator is not part of the currently active validator set.\n\nThe query response type is `ValidatorResponse`.", + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The validator's address (e.g. (e.g. cosmosvaloper1...))", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "SubMsgResponse": { + "description": "The information we get back from a successful sub message execution, with full Cosmos SDK events.", + "type": "object", + "required": [ + "events" + ], + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "events": { + "type": "array", + "items": { + "$ref": "#/definitions/Event" + } + } + } + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "WasmQuery": { + "oneOf": [ + { + "description": "this queries the public API of another contract at a known address (with known ABI) Return value is whatever the contract returns (caller should know), wrapped in a ContractResult that is JSON encoded.", + "type": "object", + "required": [ + "smart" + ], + "properties": { + "smart": { + "type": "object", + "required": [ + "contract_addr", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded QueryMsg struct", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "this queries the raw kv-store of the contract. returns the raw, unparsed data stored at that key, which may be an empty vector if not present", + "type": "object", + "required": [ + "raw" + ], + "properties": { + "raw": { + "type": "object", + "required": [ + "contract_addr", + "key" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "key": { + "description": "Key is the raw key used in the contracts Storage", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns a [`ContractInfoResponse`] with metadata on the contract from the runtime", + "type": "object", + "required": [ + "contract_info" + ], + "properties": { + "contract_info": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns a [`CodeInfoResponse`] with metadata of the code", + "type": "object", + "required": [ + "code_info" + ], + "properties": { + "code_info": { + "type": "object", + "required": [ + "code_id" + ], + "properties": { + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/instantiate_msg.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/instantiate_msg.json new file mode 100644 index 000000000..accd66ba8 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/instantiate_msg.json @@ -0,0 +1,122 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "base", + "module" + ], + "properties": { + "base": { + "description": "base instantiate information", + "allOf": [ + { + "$ref": "#/definitions/BaseInstantiateMsg" + } + ] + }, + "module": { + "description": "custom instantiate msg", + "allOf": [ + { + "$ref": "#/definitions/LstAdapterInstantiateMsg" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "AccountBase": { + "description": "Contains the minimal Abstract Account contract addresses.", + "type": "object", + "required": [ + "manager", + "proxy" + ], + "properties": { + "manager": { + "$ref": "#/definitions/Addr" + }, + "proxy": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + }, + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "BaseInstantiateMsg": { + "description": "Used by Module Factory to instantiate App", + "type": "object", + "required": [ + "account_base", + "ans_host_address", + "version_control_address" + ], + "properties": { + "account_base": { + "$ref": "#/definitions/AccountBase" + }, + "ans_host_address": { + "type": "string" + }, + "version_control_address": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Denoms": { + "type": "object", + "required": [ + "lst", + "underlying" + ], + "properties": { + "lst": { + "type": "string" + }, + "underlying": { + "type": "string" + } + }, + "additionalProperties": false + }, + "LstAdapterInstantiateMsg": { + "type": "object", + "required": [ + "denoms", + "observer", + "owner", + "stride_oracle", + "unbond_period_secs", + "vault" + ], + "properties": { + "denoms": { + "$ref": "#/definitions/Denoms" + }, + "observer": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "stride_oracle": { + "type": "string" + }, + "unbond_period_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vault": { + "type": "string" + } + }, + "additionalProperties": false + } + } +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/migrate_msg.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/migrate_msg.json new file mode 100644 index 000000000..7dc42a500 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/migrate_msg.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "required": [ + "base", + "module" + ], + "properties": { + "base": { + "description": "base migrate information", + "allOf": [ + { + "$ref": "#/definitions/BaseMigrateMsg" + } + ] + }, + "module": { + "description": "custom migrate msg", + "allOf": [ + { + "$ref": "#/definitions/LstAdapterMigrateMsg" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "BaseMigrateMsg": { + "type": "object", + "additionalProperties": false + }, + "LstAdapterMigrateMsg": { + "type": "object", + "additionalProperties": false + } + } +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/module-schema.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/module-schema.json new file mode 100644 index 000000000..776a3dca8 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/module-schema.json @@ -0,0 +1,621 @@ +{ + "contract_name": "module-schema", + "contract_version": "0.22.3", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "denoms", + "observer", + "owner", + "stride_oracle", + "unbond_period_secs", + "vault" + ], + "properties": { + "denoms": { + "$ref": "#/definitions/Denoms" + }, + "observer": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "stride_oracle": { + "type": "string" + }, + "unbond_period_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vault": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Denoms": { + "type": "object", + "required": [ + "lst", + "underlying" + ], + "properties": { + "lst": { + "type": "string" + }, + "underlying": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "unbond" + ], + "properties": { + "unbond": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "confirm_unbond" + ], + "properties": { + "confirm_unbond": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "confirm_unbond_finished" + ], + "properties": { + "confirm_unbond_finished": { + "type": "object", + "required": [ + "unbond_start_time" + ], + "properties": { + "unbond_start_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_ibc_config" + ], + "properties": { + "update_ibc_config": { + "type": "object", + "required": [ + "channel", + "remote_chain" + ], + "properties": { + "block_offset": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "channel": { + "type": "string" + }, + "remote_chain": { + "type": "string" + }, + "revision": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "timeout_secs": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update" + ], + "properties": { + "update": { + "type": "object", + "properties": { + "denoms": { + "anyOf": [ + { + "$ref": "#/definitions/Denoms" + }, + { + "type": "null" + } + ] + }, + "observer": { + "type": [ + "string", + "null" + ] + }, + "stride_oracle": { + "type": [ + "string", + "null" + ] + }, + "unbond_period_secs": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "vault": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_owner" + ], + "properties": { + "update_owner": { + "$ref": "#/definitions/OwnerUpdate" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Denoms": { + "type": "object", + "required": [ + "lst", + "underlying" + ], + "properties": { + "lst": { + "type": "string" + }, + "underlying": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OwnerUpdate": { + "oneOf": [ + { + "description": "Proposes a new owner to take role. Only current owner can execute.", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "proposed" + ], + "properties": { + "proposed": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Clears the currently proposed owner. Only current owner can execute.", + "type": "string", + "enum": [ + "clear_proposed" + ] + }, + { + "description": "Promotes the proposed owner to be the current one. Only the proposed owner can execute.", + "type": "string", + "enum": [ + "accept_proposed" + ] + }, + { + "description": "Throws away the keys to the Owner role forever. Once done, no owner can ever be set later.", + "type": "string", + "enum": [ + "abolish_owner_role" + ] + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "ibc_config" + ], + "properties": { + "ibc_config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "oracle" + ], + "properties": { + "oracle": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "denoms" + ], + "properties": { + "denoms": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "redemption_rate" + ], + "properties": { + "redemption_rate": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pending_unbonds" + ], + "properties": { + "pending_unbonds": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "balance_in_underlying" + ], + "properties": { + "balance_in_underlying": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claimable" + ], + "properties": { + "claimable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false + }, + "sudo": null, + "responses": { + "balance_in_underlying": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "claimable": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "denoms": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Denoms", + "type": "object", + "required": [ + "lst", + "underlying" + ], + "properties": { + "lst": { + "type": "string" + }, + "underlying": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ibc_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IbcConfig", + "type": "object", + "required": [ + "channel", + "remote_chain" + ], + "properties": { + "block_offset": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "channel": { + "type": "string" + }, + "remote_chain": { + "type": "string" + }, + "revision": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "timeout_secs": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "oracle": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "String", + "type": "string" + }, + "owner": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "String", + "type": "string" + }, + "pending_unbonds": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_UnbondInfo", + "type": "array", + "items": { + "$ref": "#/definitions/UnbondInfo" + }, + "definitions": { + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "UnbondInfo": { + "type": "object", + "required": [ + "amount", + "status", + "unbond_start" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "status": { + "$ref": "#/definitions/UnbondStatus" + }, + "unbond_start": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + "UnbondStatus": { + "type": "string", + "enum": [ + "unconfirmed", + "confirmed" + ] + } + } + }, + "redemption_rate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Decimal", + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "vault": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "String", + "type": "string" + } + } +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/query_msg.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/query_msg.json new file mode 100644 index 000000000..30d5f2bc8 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/query_msg.json @@ -0,0 +1,215 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "A query to the base.", + "type": "object", + "required": [ + "base" + ], + "properties": { + "base": { + "$ref": "#/definitions/BaseQueryMsg" + } + }, + "additionalProperties": false + }, + { + "description": "Custom query", + "type": "object", + "required": [ + "module" + ], + "properties": { + "module": { + "$ref": "#/definitions/LstAdapterQueryMsg" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "BaseQueryMsg": { + "oneOf": [ + { + "description": "Returns [`AppConfigResponse`]", + "type": "object", + "required": [ + "base_config" + ], + "properties": { + "base_config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the admin. Returns [`AdminResponse`]", + "type": "object", + "required": [ + "base_admin" + ], + "properties": { + "base_admin": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns module data Returns [`ModuleDataResponse`]", + "type": "object", + "required": [ + "module_data" + ], + "properties": { + "module_data": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns top level owner Returns [`TopLevelOwnerResponse`]", + "type": "object", + "required": [ + "top_level_owner" + ], + "properties": { + "top_level_owner": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "LstAdapterQueryMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "ibc_config" + ], + "properties": { + "ibc_config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "oracle" + ], + "properties": { + "oracle": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "denoms" + ], + "properties": { + "denoms": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "redemption_rate" + ], + "properties": { + "redemption_rate": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pending_unbonds" + ], + "properties": { + "pending_unbonds": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "balance_in_underlying" + ], + "properties": { + "balance_in_underlying": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claimable" + ], + "properties": { + "claimable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/execute.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/execute.json new file mode 100644 index 000000000..e53df6338 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/execute.json @@ -0,0 +1,267 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "unbond" + ], + "properties": { + "unbond": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "confirm_unbond" + ], + "properties": { + "confirm_unbond": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "confirm_unbond_finished" + ], + "properties": { + "confirm_unbond_finished": { + "type": "object", + "required": [ + "unbond_start_time" + ], + "properties": { + "unbond_start_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_ibc_config" + ], + "properties": { + "update_ibc_config": { + "type": "object", + "required": [ + "channel", + "remote_chain" + ], + "properties": { + "block_offset": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "channel": { + "type": "string" + }, + "remote_chain": { + "type": "string" + }, + "revision": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "timeout_secs": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update" + ], + "properties": { + "update": { + "type": "object", + "properties": { + "denoms": { + "anyOf": [ + { + "$ref": "#/definitions/Denoms" + }, + { + "type": "null" + } + ] + }, + "observer": { + "type": [ + "string", + "null" + ] + }, + "stride_oracle": { + "type": [ + "string", + "null" + ] + }, + "unbond_period_secs": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "vault": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_owner" + ], + "properties": { + "update_owner": { + "$ref": "#/definitions/OwnerUpdate" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Denoms": { + "type": "object", + "required": [ + "lst", + "underlying" + ], + "properties": { + "lst": { + "type": "string" + }, + "underlying": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OwnerUpdate": { + "oneOf": [ + { + "description": "Proposes a new owner to take role. Only current owner can execute.", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "proposed" + ], + "properties": { + "proposed": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Clears the currently proposed owner. Only current owner can execute.", + "type": "string", + "enum": [ + "clear_proposed" + ] + }, + { + "description": "Promotes the proposed owner to be the current one. Only the proposed owner can execute.", + "type": "string", + "enum": [ + "accept_proposed" + ] + }, + { + "description": "Throws away the keys to the Owner role forever. Once done, no owner can ever be set later.", + "type": "string", + "enum": [ + "abolish_owner_role" + ] + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/instantiate.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/instantiate.json new file mode 100644 index 000000000..490a68634 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/instantiate.json @@ -0,0 +1,54 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "denoms", + "observer", + "owner", + "stride_oracle", + "unbond_period_secs", + "vault" + ], + "properties": { + "denoms": { + "$ref": "#/definitions/Denoms" + }, + "observer": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "stride_oracle": { + "type": "string" + }, + "unbond_period_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vault": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Denoms": { + "type": "object", + "required": [ + "lst", + "underlying" + ], + "properties": { + "lst": { + "type": "string" + }, + "underlying": { + "type": "string" + } + }, + "additionalProperties": false + } + } +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/migrate.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/migrate.json new file mode 100644 index 000000000..7fbe8c570 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/migrate.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/query.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/query.json new file mode 100644 index 000000000..ea164e0d7 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/query.json @@ -0,0 +1,123 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "ibc_config" + ], + "properties": { + "ibc_config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "oracle" + ], + "properties": { + "oracle": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "denoms" + ], + "properties": { + "denoms": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "redemption_rate" + ], + "properties": { + "redemption_rate": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pending_unbonds" + ], + "properties": { + "pending_unbonds": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "balance_in_underlying" + ], + "properties": { + "balance_in_underlying": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claimable" + ], + "properties": { + "claimable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_balance_in_underlying.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_balance_in_underlying.json new file mode 100644 index 000000000..25b73e8f2 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_balance_in_underlying.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_base_admin.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_base_admin.json new file mode 100644 index 000000000..c73969ab0 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_base_admin.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminResponse", + "description": "Returned from Admin.query_admin()", + "type": "object", + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_base_config.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_base_config.json new file mode 100644 index 000000000..0f29e9082 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_base_config.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AppConfigResponse", + "type": "object", + "required": [ + "ans_host_address", + "manager_address", + "proxy_address" + ], + "properties": { + "ans_host_address": { + "$ref": "#/definitions/Addr" + }, + "manager_address": { + "$ref": "#/definitions/Addr" + }, + "proxy_address": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_claimable.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_claimable.json new file mode 100644 index 000000000..6e18ef9a9 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_claimable.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_denoms.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_denoms.json new file mode 100644 index 000000000..6eb292d8c --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_denoms.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Denoms", + "type": "object", + "required": [ + "lst", + "underlying" + ], + "properties": { + "lst": { + "type": "string" + }, + "underlying": { + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_ibc_config.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_ibc_config.json new file mode 100644 index 000000000..ba5113a7a --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_ibc_config.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IbcConfig", + "type": "object", + "required": [ + "channel", + "remote_chain" + ], + "properties": { + "block_offset": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "channel": { + "type": "string" + }, + "remote_chain": { + "type": "string" + }, + "revision": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "timeout_secs": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_module_data.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_module_data.json new file mode 100644 index 000000000..9932c0da6 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_module_data.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ModuleDataResponse", + "type": "object", + "required": [ + "dependencies", + "module_id", + "version" + ], + "properties": { + "dependencies": { + "type": "array", + "items": { + "$ref": "#/definitions/DependencyResponse" + } + }, + "metadata": { + "type": [ + "string", + "null" + ] + }, + "module_id": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "DependencyResponse": { + "type": "object", + "required": [ + "id", + "version_req" + ], + "properties": { + "id": { + "type": "string" + }, + "version_req": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + } +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_oracle.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_oracle.json new file mode 100644 index 000000000..f689acebf --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_oracle.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "String", + "type": "string" +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_owner.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_owner.json new file mode 100644 index 000000000..f689acebf --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_owner.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "String", + "type": "string" +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_pending_unbonds.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_pending_unbonds.json new file mode 100644 index 000000000..f3a49f014 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_pending_unbonds.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_UnbondInfo", + "type": "array", + "items": { + "$ref": "#/definitions/UnbondInfo" + }, + "definitions": { + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "UnbondInfo": { + "type": "object", + "required": [ + "amount", + "status", + "unbond_start" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "status": { + "$ref": "#/definitions/UnbondStatus" + }, + "unbond_start": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + "UnbondStatus": { + "type": "string", + "enum": [ + "unconfirmed", + "confirmed" + ] + } + } +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_redemption_rate.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_redemption_rate.json new file mode 100644 index 000000000..83bac1c14 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_redemption_rate.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Decimal", + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_top_level_owner.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_top_level_owner.json new file mode 100644 index 000000000..13fc07267 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_top_level_owner.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TopLevelOwnerResponse", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_vault.json b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_vault.json new file mode 100644 index 000000000..f689acebf --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/schema/raw/response_to_vault.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "String", + "type": "string" +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/src/bin/schema.rs b/smart-contracts/contracts/lst-adapter-osmosis/src/bin/schema.rs new file mode 100644 index 000000000..f82217ecd --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/src/bin/schema.rs @@ -0,0 +1,14 @@ +use cosmwasm_schema::remove_schemas; +use lst_adapter_osmosis::contract::LstAdapter; +use std::env::current_dir; +use std::fs::create_dir_all; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + #[cfg(feature = "schema")] + LstAdapter::export_schema(&out_dir); +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/src/contract.rs b/smart-contracts/contracts/lst-adapter-osmosis/src/contract.rs new file mode 100644 index 000000000..3c6c00d0d --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/src/contract.rs @@ -0,0 +1,476 @@ +use crate::error::{assert_observer, assert_vault}; +use crate::msg::{ + LstAdapterExecuteMsg, LstAdapterInstantiateMsg, LstAdapterMigrateMsg, LstAdapterQueryMsg, +}; +use crate::state::{ + Denoms, IbcConfig, UnbondInfo, UnbondStatus, DENOMS, IBC_CONFIG, OBSERVER, ORACLE, OWNER, + REDEEMED_BALANCE, TOTAL_BALANCE, UNBONDING, UNBOND_PERIOD_SECS, VAULT, +}; +use crate::{LstAdapterError, LST_ADAPTER_OSMOSIS_ID, LST_ADAPTER_OSMOSIS_VERSION}; +#[cfg(not(target_arch = "wasm32"))] +use abstract_app::abstract_interface::AbstractInterfaceError; +use abstract_app::{abstract_interface, AppContract}; +use abstract_sdk::{AbstractResponse, IbcInterface, TransferInterface}; +#[cfg(not(target_arch = "wasm32"))] +use abstract_std::manager::ModuleInstallConfig; +use abstract_std::objects::chain_name::ChainName; +use cosmwasm_std::{ + coin, coins, to_json_binary, BankMsg, Binary, Coin, Decimal, Deps, DepsMut, Env, MessageInfo, + Response, StdError, StdResult, Storage, Timestamp, Uint128, +}; +use ica_oracle::msg::{ + QueryMsg as StrideQueryMsg, RedemptionRateResponse as StrideRedemptionRateResponse, +}; +use mars_owner::OwnerInit::SetInitialOwner; +// use osmosis_std::types::ibc::applications::transfer::v1::MsgTransfer; +// use osmosis_std::types::ibc::core::client::v1::Height; +// use prost::Message; +use quasar_types::{ + error::assert_funds_single_token, + query::query_contract_balance, + // stride::{get_autopilot_msg, Action}, +}; + +pub type LstAdapterResult = Result; + +pub type LstAdapter = AppContract< + LstAdapterError, + LstAdapterInstantiateMsg, + LstAdapterExecuteMsg, + LstAdapterQueryMsg, + LstAdapterMigrateMsg, +>; + +const APP: LstAdapter = LstAdapter::new(LST_ADAPTER_OSMOSIS_ID, LST_ADAPTER_OSMOSIS_VERSION, None) + .with_instantiate(instantiate_) + .with_execute(execute_) + .with_query(query_) + .with_migrate(migrate_); + +#[cfg(feature = "export")] +abstract_app::export_endpoints!(APP, LstAdapter); + +abstract_app::cw_orch_interface!(APP, LstAdapter, LstAdapterInterface); + +#[cfg(not(target_arch = "wasm32"))] +impl abstract_interface::DependencyCreation + for crate::LstAdapterInterface +{ + type DependenciesConfig = cosmwasm_std::Empty; + + fn dependency_install_configs( + _configuration: Self::DependenciesConfig, + ) -> Result, AbstractInterfaceError> { + Ok(vec![]) + } +} + +pub fn instantiate_( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + _app: LstAdapter, + msg: LstAdapterInstantiateMsg, +) -> LstAdapterResult { + OWNER.initialize(deps.storage, deps.api, SetInitialOwner { owner: msg.owner })?; + VAULT.save(deps.storage, &deps.api.addr_validate(&msg.vault)?)?; + OBSERVER.save(deps.storage, &deps.api.addr_validate(&msg.observer)?)?; + DENOMS.save(deps.storage, &msg.denoms)?; + ORACLE.save(deps.storage, &deps.api.addr_validate(&msg.stride_oracle)?)?; + UNBONDING.save(deps.storage, &vec![])?; + UNBOND_PERIOD_SECS.save(deps.storage, &msg.unbond_period_secs)?; + REDEEMED_BALANCE.save(deps.storage, &Uint128::zero())?; + TOTAL_BALANCE.save(deps.storage, &Uint128::zero())?; + Ok(Response::default()) +} + +pub fn execute_( + deps: DepsMut, + env: Env, + info: MessageInfo, + app: LstAdapter, + msg: LstAdapterExecuteMsg, +) -> LstAdapterResult { + match msg { + LstAdapterExecuteMsg::Unbond {} => unbond(deps, env, info, app), + LstAdapterExecuteMsg::ConfirmUnbond { amount } => confirm_unbond(deps, info, app, amount), + LstAdapterExecuteMsg::ConfirmUnbondFinished { unbond_start_time } => { + confirm_unbond_finished(deps, env, info, app, unbond_start_time) + } + LstAdapterExecuteMsg::Claim {} => claim(deps, env, info, app), + LstAdapterExecuteMsg::UpdateIbcConfig { + remote_chain, + channel, + revision, + block_offset, + timeout_secs, + } => update_ibc_config( + deps, + info, + app, + remote_chain, + channel, + revision, + block_offset, + timeout_secs, + ), + LstAdapterExecuteMsg::UpdateOwner(update) => Ok(OWNER.update(deps, info, update)?), + LstAdapterExecuteMsg::Update { + vault, + denoms, + observer, + stride_oracle, + unbond_period_secs: unbond_period, + } => update( + deps, + info, + app, + vault, + observer, + denoms, + stride_oracle, + unbond_period, + ), + } +} + +fn record_pending_unbond( + storage: &mut dyn Storage, + amount: Uint128, + time: Timestamp, +) -> Result { + let mut unbonding = UNBONDING.load(storage)?; + let pending = unbonding + .last() + .map(|info| info.status.clone()) + .unwrap_or(UnbondStatus::Confirmed) + == UnbondStatus::Unconfirmed; + if !pending { + unbonding.push(UnbondInfo { + amount, + unbond_start: time, + status: UnbondStatus::Unconfirmed, + }); + UNBONDING.save(storage, &unbonding)?; + TOTAL_BALANCE.update(storage, |balance| -> StdResult { + balance + .checked_add(amount) + .map_err(|err| StdError::GenericErr { + msg: err.to_string(), + }) + })?; + } + Ok(pending) +} + +fn unbond(deps: DepsMut, env: Env, info: MessageInfo, app: LstAdapter) -> LstAdapterResult { + assert_vault(&info.sender, &VAULT.load(deps.storage)?)?; + let denoms = DENOMS.load(deps.storage)?; + assert_funds_single_token(&info.funds, &denoms.lst)?; + let redemption_rate = query_redemption_rate(deps.as_ref())?; + let unbond_amount = query_contract_balance(&deps.querier, &env, &denoms.lst)?; + let previous_unbond_pending = record_pending_unbond( + deps.storage, + unbond_amount.checked_mul_floor(redemption_rate)?, + env.block.time, + )?; + + let response = app.response("unbond"); + if previous_unbond_pending { + return Ok(response); + } + + let unbond_funds = coins(unbond_amount.into(), denoms.lst); + let mut transfer_msgs = app.bank(deps.as_ref()).deposit(unbond_funds.clone())?; + let ibc_client = app.ibc_client(deps.as_ref()); + let ibc_msg = ibc_client.ics20_transfer( + ChainName::from_chain_id("stargaze-1").to_string(), + unbond_funds, + )?; + transfer_msgs.push(ibc_msg); + + // let ibc_config = IBC_CONFIG.load(deps.storage)?; + // let remote_addr = app + // .ibc_client(deps.as_ref()) + // .remote_proxy_addr(&ibc_config.remote_chain)?; + // if remote_addr.is_none() { + // return Err(LstAdapterError::MissingRemoteAddress { + // chain: ibc_config.remote_chain, + // }); + // } + // let remote_addr = remote_addr.unwrap(); + // let autopilot_redeem_msg = get_autopilot_msg( + // Action::RedeemStake, + // remote_addr.as_ref(), + // Some(env.contract.address.to_string()), + // ); + // let timeout_timestamp = if let Some(timeout_secs) = ibc_config.timeout_secs { env.block.time.nanos() + timeout_secs * 1_000_000_000 } else { 0u64 }; + // let msg = MsgTransfer { + // source_port: "transfer".to_string(), + // source_channel: ibc_config.channel, + // token: Some(info.funds[0].clone().into()), + // sender: env.contract.address.to_string(), + // receiver: remote_addr, + // timeout_height: Some(Height { + // revision_number: 5, + // revision_height: env.block.height + 5, + // }), + // timeout_timestamp, + // memo: serde_json::to_string(&autopilot_redeem_msg) + // .map_err(|err| LstAdapterError::Json(err.to_string()))?, + // }; + // Ok(app.response("unbond").add_message(msg)) + Ok(response.add_messages(transfer_msgs)) +} + +fn adjust_total_balance( + storage: &mut dyn Storage, + amount: Uint128, + final_amount: Uint128, +) -> StdResult<()> { + TOTAL_BALANCE.update(storage, |balance| -> StdResult { + balance + .checked_add(final_amount) + .map_err(|err| StdError::GenericErr { + msg: err.to_string(), + })? + .checked_sub(amount) + .map_err(|err| StdError::GenericErr { + msg: err.to_string(), + }) + })?; + Ok(()) +} + +fn confirm_unbond( + deps: DepsMut, + info: MessageInfo, + app: LstAdapter, + amount: Uint128, +) -> LstAdapterResult { + assert_observer(&info.sender, &OBSERVER.load(deps.storage)?)?; + let mut unbonding = UNBONDING.load(deps.storage)?; + let last = unbonding.last_mut(); + if let Some(last) = last { + if last.status == UnbondStatus::Unconfirmed { + last.status = UnbondStatus::Confirmed; + adjust_total_balance(deps.storage, last.amount, amount)?; + last.amount = amount; + UNBONDING.save(deps.storage, &unbonding)?; + + return Ok(app.response("confirm unbond")); + } + } + + Err(LstAdapterError::NothingToConfirm {}) +} + +fn confirm_unbond_finished( + deps: DepsMut, + env: Env, + info: MessageInfo, + app: LstAdapter, + unbond_start_time: Timestamp, +) -> LstAdapterResult { + assert_observer(&info.sender, &OBSERVER.load(deps.storage)?)?; + let mut unbonding = UNBONDING.load(deps.storage)?; + let pos = unbonding.iter().position(|info| { + info.status == UnbondStatus::Confirmed && info.unbond_start == unbond_start_time + }); + if let Some(pos) = pos { + let unbond_info = unbonding.remove(pos); + let unbond_period_secs = UNBOND_PERIOD_SECS.load(deps.storage)?; + if unbond_info.unbond_start.seconds() + unbond_period_secs > env.block.time.seconds() { + return Err(LstAdapterError::UnbondNotFinished {}); + } + let mut redeemed_balance = REDEEMED_BALANCE.load(deps.storage)?; + let denoms = DENOMS.load(deps.storage)?; + let contract_balance = query_contract_balance(&deps.querier, &env, &denoms.underlying)?; + redeemed_balance += unbond_info.amount; + if redeemed_balance > contract_balance { + return Err(LstAdapterError::StillWaitingForFunds {}); + } + REDEEMED_BALANCE.save(deps.storage, &redeemed_balance)?; + UNBONDING.save(deps.storage, &unbonding)?; + return Ok(app.response("confirm unbond finished")); + } + + Err(LstAdapterError::NoPendingUnbond {}) +} + +fn claim(deps: DepsMut, env: Env, info: MessageInfo, app: LstAdapter) -> LstAdapterResult { + assert_vault(&info.sender, &VAULT.load(deps.storage)?)?; + let claimable = get_claimable(&deps.as_ref(), &env)?; + + if claimable.amount.is_zero() { + return Err(LstAdapterError::NothingToClaim {}); + } + + let redeemed_balance = REDEEMED_BALANCE.load(deps.storage)?; + REDEEMED_BALANCE.save(deps.storage, &Uint128::zero())?; + let msg = BankMsg::Send { + to_address: info.sender.to_string(), + amount: vec![claimable], + }; + + TOTAL_BALANCE.update(deps.storage, |balance| -> StdResult { + balance + .checked_sub(redeemed_balance) + .map_err(|err| StdError::GenericErr { + msg: err.to_string(), + }) + })?; + Ok(app.response("claim").add_message(msg)) +} + +#[allow(clippy::too_many_arguments)] +fn update_ibc_config( + deps: DepsMut, + info: MessageInfo, + app: LstAdapter, + remote_chain: String, + channel: String, + revision: Option, + block_offset: Option, + timeout_secs: Option, +) -> LstAdapterResult { + OWNER.assert_owner(deps.storage, &info.sender)?; + IBC_CONFIG.save( + deps.storage, + &IbcConfig { + remote_chain, + channel, + revision, + block_offset, + timeout_secs, + }, + )?; + Ok(app.response("update ibc config")) +} + +#[allow(clippy::too_many_arguments)] +fn update( + deps: DepsMut, + info: MessageInfo, + app: LstAdapter, + vault: Option, + observer: Option, + denoms: Option, + stride_oracle: Option, + unbond_period: Option, +) -> LstAdapterResult { + OWNER.assert_owner(deps.storage, &info.sender)?; + if let Some(vault) = vault { + VAULT.save(deps.storage, &deps.api.addr_validate(&vault)?)?; + } + if let Some(observer) = observer { + OBSERVER.save(deps.storage, &deps.api.addr_validate(&observer)?)?; + } + if let Some(denoms) = denoms { + DENOMS.save(deps.storage, &denoms)?; + } + if let Some(stride_oracle) = stride_oracle { + ORACLE.save(deps.storage, &deps.api.addr_validate(&stride_oracle)?)?; + } + if let Some(unbond_period) = unbond_period { + UNBOND_PERIOD_SECS.save(deps.storage, &unbond_period)?; + } + Ok(app.response("update")) +} + +fn get_balance(deps: Deps, env: Env) -> Result { + let total_balance = TOTAL_BALANCE.load(deps.storage)?; + // we only update the total balance when we start the unbonding process + // therefore we have to add the underlying tokens corresponding to the lst tokens that are hold by this contract + let denoms = DENOMS.load(deps.storage)?; + let lst_balance = query_contract_balance(&deps.querier, &env, &denoms.lst)?; + let redemption_rate = query_redemption_rate(deps)?; + Ok(total_balance.checked_add(lst_balance.checked_mul_floor(redemption_rate)?)?) +} + +pub fn query_( + deps: Deps, + env: Env, + _app: &LstAdapter, + msg: LstAdapterQueryMsg, +) -> LstAdapterResult { + match msg { + LstAdapterQueryMsg::IbcConfig {} => Ok(to_json_binary( + &IBC_CONFIG.may_load(deps.storage)?.unwrap_or_default(), + )?), + LstAdapterQueryMsg::Owner {} => Ok(to_json_binary( + &OWNER + .current(deps.storage)? + .map(String::from) + .unwrap_or_default(), + )?), + LstAdapterQueryMsg::Vault {} => Ok(to_json_binary(&VAULT.load(deps.storage)?.to_string())?), + LstAdapterQueryMsg::Oracle {} => { + Ok(to_json_binary(&ORACLE.load(deps.storage)?.to_string())?) + } + LstAdapterQueryMsg::Denoms {} => Ok(to_json_binary(&DENOMS.load(deps.storage)?)?), + LstAdapterQueryMsg::RedemptionRate {} => Ok(to_json_binary(&query_redemption_rate(deps)?)?), + LstAdapterQueryMsg::PendingUnbonds {} => { + Ok(to_json_binary(&UNBONDING.load(deps.storage)?)?) + } + LstAdapterQueryMsg::BalanceInUnderlying {} => Ok(to_json_binary(&get_balance(deps, env)?)?), + LstAdapterQueryMsg::Claimable {} => Ok(to_json_binary(&get_claimable(&deps, &env)?)?), + } +} + +fn get_underlying_balance(deps: &Deps, env: &Env) -> StdResult { + let denoms = DENOMS.load(deps.storage)?; + Ok(coin( + query_contract_balance(&deps.querier, env, &denoms.underlying)?.into(), + denoms.underlying, + )) +} + +fn get_info_amount(info: &UnbondInfo) -> Uint128 { + info.amount +} + +fn query_redemption_rate(deps: Deps) -> StdResult { + let response: StrideRedemptionRateResponse = deps.querier.query_wasm_smart( + ORACLE.load(deps.storage)?, + &StrideQueryMsg::RedemptionRate { + denom: DENOMS.load(deps.storage)?.lst, + params: None, + }, + )?; + Ok(response.redemption_rate) +} + +fn get_claimable(deps: &Deps, env: &Env) -> LstAdapterResult { + let mut underlying_balance = get_underlying_balance(deps, env)?; + let unbonding = UNBONDING.load(deps.storage)?; + let unbond_period_secs = UNBOND_PERIOD_SECS.load(deps.storage)?; + let unbonding_expired: Vec = unbonding + .into_iter() + .filter(|info| info.unbond_start.plus_seconds(unbond_period_secs) <= env.block.time) + .collect(); + + // if there are expired unbondings for which we didn't get a confirmation yet we have to block the associated tokens + let claimable = if unbonding_expired.is_empty() { + underlying_balance + } else { + let blocked = unbonding_expired.iter().map(get_info_amount).sum(); + if blocked < underlying_balance.amount { + underlying_balance.amount = underlying_balance.amount.checked_sub(blocked)?; + underlying_balance + } else { + coin(0u128, underlying_balance.denom) + } + }; + + Ok(claimable) +} + +pub fn migrate_( + _deps: DepsMut, + _env: Env, + app: LstAdapter, + _msg: LstAdapterMigrateMsg, +) -> LstAdapterResult { + Ok(app.response("migrate")) +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/src/error.rs b/smart-contracts/contracts/lst-adapter-osmosis/src/error.rs new file mode 100644 index 000000000..a098f0f3c --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/src/error.rs @@ -0,0 +1,78 @@ +use abstract_app::sdk::AbstractSdkError; +use abstract_app::std::AbstractError; +use abstract_app::AppError; +use cosmwasm_std::{Addr, CheckedMultiplyFractionError, OverflowError, StdError}; +use mars_owner::OwnerError; +use quasar_types::error::FundsError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum LstAdapterError { + #[error("{0}")] + Abstract(#[from] AbstractError), + + #[error("{0}")] + AbstractSdk(#[from] AbstractSdkError), + + #[error("{0}")] + DappError(#[from] AppError), + + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + Overflow(#[from] OverflowError), + + #[error("{0}")] + CheckedMultiply(#[from] CheckedMultiplyFractionError), + + #[error("{0}")] + Owner(#[from] OwnerError), + + #[error("{0}")] + Funds(#[from] FundsError), + + #[error("{0}")] + Json(String), + + #[error("Only configured vault can unbond or claim.")] + NotVault {}, + + #[error("Only configured observer can confirm transactions.")] + NotObserver {}, + + #[error("Missing remote address for {chain}")] + MissingRemoteAddress { chain: String }, + + #[error("Nothing to claim.")] + NothingToClaim {}, + + #[error("Unconfirmed unbond pending.")] + UnconfirmedUnbondPending {}, + + #[error("Nothing to confirm.")] + NothingToConfirm {}, + + #[error("Unbond is not finished.")] + UnbondNotFinished {}, + + #[error("No pending unbond.")] + NoPendingUnbond {}, + + #[error("Can't confirm unbond without funds being available.")] + StillWaitingForFunds {}, +} + +pub fn assert_vault(sender: &Addr, vault: &Addr) -> Result<(), LstAdapterError> { + if sender != vault { + return Err(LstAdapterError::NotVault {}); + } + Ok(()) +} + +pub fn assert_observer(sender: &Addr, observer: &Addr) -> Result<(), LstAdapterError> { + if sender != observer { + return Err(LstAdapterError::NotObserver {}); + } + Ok(()) +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/src/lib.rs b/smart-contracts/contracts/lst-adapter-osmosis/src/lib.rs new file mode 100644 index 000000000..c2bbdb5a9 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/src/lib.rs @@ -0,0 +1,18 @@ +pub mod contract; +mod error; +pub mod msg; +pub mod state; + +pub use crate::error::LstAdapterError; + +#[cfg(test)] +mod tests; + +pub const LST_ADAPTER_OSMOSIS_VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub use contract::interface::LstAdapterInterface; + +pub const LST_ADAPTER_OSMOSIS_NAMESPACE: &str = "quasar"; +pub const LST_ADAPTER_OSMOSIS_NAME: &str = "lst-adapter-osmosis"; +pub const LST_ADAPTER_OSMOSIS_ID: &str = + const_format::formatcp!("{LST_ADAPTER_OSMOSIS_NAMESPACE}:{LST_ADAPTER_OSMOSIS_NAME}"); diff --git a/smart-contracts/contracts/lst-adapter-osmosis/src/msg.rs b/smart-contracts/contracts/lst-adapter-osmosis/src/msg.rs new file mode 100644 index 000000000..6ea3b0e3e --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/src/msg.rs @@ -0,0 +1,77 @@ +use crate::{ + contract::LstAdapter, + state::{Denoms, IbcConfig, UnbondInfo}, +}; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Coin, Decimal, Timestamp, Uint128}; +use mars_owner::OwnerUpdate; + +abstract_app::app_msg_types!(LstAdapter, LstAdapterExecuteMsg, LstAdapterQueryMsg); + +#[cw_serde] +pub struct LstAdapterInstantiateMsg { + pub owner: String, + pub vault: String, + pub observer: String, + pub denoms: Denoms, + pub stride_oracle: String, + pub unbond_period_secs: u64, +} + +#[cw_serde] +pub struct LstAdapterMigrateMsg {} + +#[cw_serde] +#[derive(cw_orch::ExecuteFns)] +pub enum LstAdapterExecuteMsg { + // vault methods + #[cw_orch(payable)] + Unbond {}, + Claim {}, + // observer methods + ConfirmUnbond { + amount: Uint128, + }, + ConfirmUnbondFinished { + unbond_start_time: Timestamp, + }, + // owner methods + UpdateIbcConfig { + channel: String, + remote_chain: String, + revision: Option, + block_offset: Option, + timeout_secs: Option, + }, + Update { + denoms: Option, + stride_oracle: Option, + vault: Option, + observer: Option, + unbond_period_secs: Option, + }, + UpdateOwner(OwnerUpdate), +} + +#[cw_serde] +#[derive(cw_orch::QueryFns, QueryResponses)] +pub enum LstAdapterQueryMsg { + #[returns(IbcConfig)] + IbcConfig {}, + #[returns(String)] + Owner {}, + #[returns(String)] + Vault {}, + #[returns(String)] + Oracle {}, + #[returns(Denoms)] + Denoms {}, + #[returns(Decimal)] + RedemptionRate {}, + #[returns(Vec)] + PendingUnbonds {}, + #[returns(Uint128)] + BalanceInUnderlying {}, + #[returns(Coin)] + Claimable {}, +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/src/state.rs b/smart-contracts/contracts/lst-adapter-osmosis/src/state.rs new file mode 100644 index 000000000..a0194040e --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/src/state.rs @@ -0,0 +1,47 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Timestamp, Uint128}; +use cw_storage_plus::Item; +use mars_owner::Owner; + +#[derive(Default)] +#[cw_serde] +pub struct IbcConfig { + pub channel: String, + pub remote_chain: String, + pub revision: Option, + pub block_offset: Option, + pub timeout_secs: Option, +} + +#[cw_serde] +pub enum UnbondStatus { + Unconfirmed, + Confirmed, +} + +#[cw_serde] +pub struct UnbondInfo { + pub amount: Uint128, + pub unbond_start: Timestamp, + pub status: UnbondStatus, +} + +#[cw_serde] +pub struct Denoms { + pub lst: String, + pub underlying: String, +} + +// configuration +pub const DENOMS: Item = Item::new("denoms"); +pub const UNBOND_PERIOD_SECS: Item = Item::new("unbond_period"); +pub const OWNER: Owner = Owner::new("owner"); +pub const VAULT: Item = Item::new("vault"); +pub const OBSERVER: Item = Item::new("observer"); +pub const IBC_CONFIG: Item = Item::new("ibc_config"); +pub const ORACLE: Item = Item::new("stride_oracle"); +// info on pending unbondings +pub const UNBONDING: Item> = Item::new("unbonding"); +// for balance tracking +pub const REDEEMED_BALANCE: Item = Item::new("underlying_balance"); +pub const TOTAL_BALANCE: Item = Item::new("total_balance"); diff --git a/smart-contracts/contracts/lst-adapter-osmosis/src/tests/fake_stride_oracle/mod.rs b/smart-contracts/contracts/lst-adapter-osmosis/src/tests/fake_stride_oracle/mod.rs new file mode 100644 index 000000000..9b03ae9bb --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/src/tests/fake_stride_oracle/mod.rs @@ -0,0 +1,123 @@ +use abstract_app::objects::ans_host::AnsHostError; +use abstract_app::sdk::AbstractSdkError; +use abstract_app::std::AbstractError; +use abstract_app::AppError; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::StdError; +use cosmwasm_std::{to_json_binary, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Response}; +use cw_orch::prelude::*; +use cw_storage_plus::Item; +use ica_oracle::msg::RedemptionRateResponse; +use thiserror::Error; + +pub const REDEMPTION_RATE: Item = Item::new("redemption_rate"); +pub const LAST_UPDATE: Item = Item::new("last_update"); + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + Abstract(#[from] AbstractError), + + #[error("{0}")] + AbstractSdk(#[from] AbstractSdkError), + + #[error("{0}")] + AnsHost(#[from] AnsHostError), + + #[error("{0}")] + DappError(#[from] AppError), +} + +#[cw_serde] +pub struct FakeStrideOracleInstantiateMsg { + pub redemption_rate: Decimal, +} + +#[cw_serde] +pub struct FakeStrideOracleMigrateMsg {} + +#[cw_serde] +#[derive(cw_orch::ExecuteFns)] +pub enum FakeStrideOracleExecuteMsg { + Update { + redemption_rate: Decimal, + last_update: u64, + }, +} + +#[cw_serde] +pub enum FakeStrideOracleQueryMsg { + RedemptionRate { denom: String, params: Option }, +} + +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: FakeStrideOracleInstantiateMsg, +) -> Result { + REDEMPTION_RATE.save(deps.storage, &msg.redemption_rate)?; + LAST_UPDATE.save(deps.storage, &0u64)?; + + Ok(Response::new()) +} + +pub fn execute( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: FakeStrideOracleExecuteMsg, +) -> Result { + match msg { + FakeStrideOracleExecuteMsg::Update { + redemption_rate, + last_update, + } => { + REDEMPTION_RATE.update(deps.storage, |_| -> Result<_, ContractError> { + Ok(redemption_rate) + })?; + LAST_UPDATE.save(deps.storage, &last_update)?; + } + } + Ok(Response::new()) +} + +pub fn query( + deps: Deps, + _env: Env, + msg: FakeStrideOracleQueryMsg, +) -> Result { + match msg { + FakeStrideOracleQueryMsg::RedemptionRate { .. } => { + Ok(to_json_binary(&RedemptionRateResponse { + redemption_rate: REDEMPTION_RATE.load(deps.storage)?, + update_time: LAST_UPDATE.load(deps.storage)?, + })?) + } + } +} + +pub fn migrate( + _deps: DepsMut, + _env: Env, + _msg: FakeStrideOracleMigrateMsg, +) -> Result { + Ok(Response::default()) +} + +#[cw_orch::interface( + FakeStrideOracleInstantiateMsg, + FakeStrideOracleExecuteMsg, + FakeStrideOracleQueryMsg, + FakeStrideOracleMigrateMsg +)] +pub struct FakeStrideOracle; + +impl Uploadable for FakeStrideOracle { + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query).with_migrate(migrate)) + } +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/src/tests/ibc_setup.rs b/smart-contracts/contracts/lst-adapter-osmosis/src/tests/ibc_setup.rs new file mode 100644 index 000000000..b17a74d42 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/src/tests/ibc_setup.rs @@ -0,0 +1,299 @@ +use crate::state::Denoms; +use crate::tests::fake_stride_oracle::{FakeStrideOracle, FakeStrideOracleInstantiateMsg}; +use abstract_cw_orch_polytone::Polytone; +use abstract_interface::{ + Abstract, AbstractAccount, AccountDetails, AppDeployer, DeployStrategy, ManagerExecFns, + ManagerQueryFns, VCExecFns, +}; +use abstract_polytone::handshake::POLYTONE_VERSION; +use abstract_std::ibc_client::{ExecuteMsgFns, QueryMsgFns}; +use abstract_std::objects::UncheckedChannelEntry; +use abstract_std::objects::{account::AccountTrace, chain_name::ChainName, AccountId}; +use abstract_std::ICS20; +use cosmwasm_std::Decimal; +use cw_orch::mock::cw_multi_test::MockApiBech32; +use cw_orch::mock::MockBase; +use cw_orch::{anyhow, prelude::*}; +use cw_orch_interchain::{prelude::*, InterchainError}; +use ibc_relayer_types::core::ics24_host::identifier::PortId; +use std::str::FromStr; + +use crate::msg::LstAdapterInstantiateMsg; +use crate::{ + LstAdapterInterface, LST_ADAPTER_OSMOSIS_ID, LST_ADAPTER_OSMOSIS_NAMESPACE, + LST_ADAPTER_OSMOSIS_VERSION, +}; + +const TEST_ACCOUNT_NAME: &str = "account-test"; +const TEST_ACCOUNT_DESCRIPTION: &str = "Description of an account"; +const TEST_ACCOUNT_LINK: &str = "https://google.com"; +pub const UNBOND_PERIOD: u64 = 1234u64; +pub const REDEMPTION_RATE_PERCENT: u64 = 123; + +pub fn create_test_remote_account>( + abstr_origin: &Abstract, + origin_id: &str, + remote_id: &str, + interchain: &IBC, + funds: Option>, +) -> anyhow::Result<(AbstractAccount, AccountId)> { + let origin_name = ChainName::from_chain_id(origin_id).to_string(); + let remote_name = ChainName::from_chain_id(remote_id).to_string(); + + // Create a local account for testing + let account_name = TEST_ACCOUNT_NAME.to_string(); + let description = Some(TEST_ACCOUNT_DESCRIPTION.to_string()); + let link = Some(TEST_ACCOUNT_LINK.to_string()); + let origin_account = abstr_origin.account_factory.create_new_account( + AccountDetails { + name: account_name.clone(), + description: description.clone(), + link: link.clone(), + base_asset: None, + install_modules: vec![], + namespace: None, + account_id: None, + }, + abstract_std::objects::gov_type::GovernanceDetails::Monarchy { + monarch: abstr_origin + .version_control + .get_chain() + .sender() + .to_string(), + }, + funds.as_deref(), + )?; + + // We need to enable ibc on the account. + origin_account.manager.update_settings(Some(true))?; + + // Now we send a message to the client saying that we want to create an account on the + // destination chain + let register_tx = origin_account.register_remote_account(&remote_name)?; + + let _ = interchain.wait_ibc(origin_id, register_tx)?; + + // After this is all ended, we return the account id of the account we just created on the remote chain + let account_config = origin_account.manager.config()?; + let remote_account_id = AccountId::new( + account_config.account_id.seq(), + AccountTrace::Remote(vec![ChainName::from_str(&origin_name)?]), + )?; + + Ok((origin_account, remote_account_id)) +} + +pub fn abstract_ibc_connection_with>( + abstr: &Abstract, + interchain: &IBC, + dest: &Abstract, + polytone_src: &Polytone, +) -> Result<(), InterchainError> { + // First we register client and host respectively + let chain1_id = abstr.ibc.client.get_chain().chain_id(); + let chain1_name = ChainName::from_chain_id(&chain1_id); + + let chain2_id = dest.ibc.client.get_chain().chain_id(); + let chain2_name = ChainName::from_chain_id(&chain2_id); + + // First, we register the host with the client. + // We register the polytone note with it because they are linked + // This triggers an IBC message that is used to get back the proxy address + let proxy_tx_result = abstr.ibc.client.register_infrastructure( + chain2_name.to_string(), + dest.ibc.host.address()?.to_string(), + polytone_src.note.address()?.to_string(), + )?; + // We make sure the IBC execution is done so that the proxy address is saved inside the Abstract contract + let _ = interchain.wait_ibc(&chain1_id, proxy_tx_result).unwrap(); + + // Finally, we get the proxy address and register the proxy with the ibc host for the dest chain + let proxy_address = abstr.ibc.client.host(chain2_name.to_string())?; + + abstract_std::ibc_host::ExecuteMsgFns::register_chain_proxy( + &dest.ibc.host, + chain1_name.to_string(), + proxy_address.remote_polytone_proxy.unwrap(), + )?; + + abstract_interface::AccountFactoryExecFns::update_config( + &dest.account_factory, + None, + Some(dest.ibc.host.address()?.to_string()), + None, + None, + )?; + + Ok(()) +} + +pub fn ibc_connect_polytone_and_abstract>( + interchain: &IBC, + origin_chain_id: &str, + remote_chain_id: &str, +) -> anyhow::Result<()> { + let origin_chain = interchain.chain(origin_chain_id).unwrap(); + let remote_chain = interchain.chain(remote_chain_id).unwrap(); + + let abstr_origin = Abstract::load_from(origin_chain.clone())?; + let abstr_remote = Abstract::load_from(remote_chain.clone())?; + + let origin_polytone = Polytone::load_from(origin_chain.clone())?; + let remote_polytone = Polytone::load_from(remote_chain.clone())?; + + // Creating a connection between 2 polytone deployments + interchain.create_contract_channel( + &origin_polytone.note, + &remote_polytone.voice, + POLYTONE_VERSION, + None, // Unordered channel + )?; + // Create the connection between client and host + abstract_ibc_connection_with(&abstr_origin, interchain, &abstr_remote, &origin_polytone)?; + Ok(()) +} + +pub fn ibc_abstract_setup>( + interchain: &IBC, + origin_chain_id: &str, + remote_chain_id: &str, +) -> anyhow::Result<(Abstract, Abstract)> { + let origin_chain = interchain.chain(origin_chain_id).unwrap(); + let remote_chain = interchain.chain(remote_chain_id).unwrap(); + + // Deploying abstract and the IBC abstract logic + let abstr_origin = + Abstract::deploy_on(origin_chain.clone(), origin_chain.sender().to_string())?; + let abstr_remote = + Abstract::deploy_on(remote_chain.clone(), remote_chain.sender().to_string())?; + + // Deploying polytone on both chains + Polytone::deploy_on(origin_chain.clone(), None)?; + Polytone::deploy_on(remote_chain.clone(), None)?; + + ibc_connect_polytone_and_abstract(interchain, origin_chain_id, remote_chain_id)?; + + Ok((abstr_origin, abstr_remote)) +} + +pub const LST_DENOM: &str = "lst_denom"; +pub const DENOM: &str = "uosmo"; +pub const STARGAZE: &str = "stargaze-1"; +pub const OSMOSIS: &str = "osmosis-1"; + +pub struct TestEnv { + pub app: LstAdapterInterface>, + pub mock: MockBech32InterchainEnv, + pub origin_account: AbstractAccount>, + pub remote_account_id: AccountId, + pub abstr_remote: Abstract>, + pub oracle_app: FakeStrideOracle>, +} + +pub fn create_app(sender_balance: Vec, vault: Option) -> anyhow::Result { + let mock = MockBech32InterchainEnv::new(vec![(OSMOSIS, "osmosis"), (STARGAZE, "stargaze")]); + + let (abstr_origin, abstr_remote) = ibc_abstract_setup(&mock, OSMOSIS, STARGAZE)?; + ibc_connect_polytone_and_abstract(&mock, STARGAZE, OSMOSIS)?; + + let (origin_account, remote_account_id) = + create_test_remote_account(&abstr_origin, OSMOSIS, STARGAZE, &mock, None)?; + let vault = if let Some(vault) = vault { + mock.chain(OSMOSIS)?.addr_make(vault) + } else { + mock.chain(OSMOSIS)?.sender() + }; + let owner = mock.chain(OSMOSIS)?.sender(); + + if !sender_balance.is_empty() { + mock.chain(OSMOSIS)? + .set_balance(&mock.chain(OSMOSIS)?.sender(), sender_balance)?; + } + + let app = LstAdapterInterface::new( + LST_ADAPTER_OSMOSIS_ID, + abstr_origin.version_control.get_chain().clone(), + ); + + abstr_origin.version_control.claim_namespace( + origin_account.id()?, + LST_ADAPTER_OSMOSIS_NAMESPACE.to_owned(), + )?; + + app.deploy(LST_ADAPTER_OSMOSIS_VERSION.parse()?, DeployStrategy::Try)?; + + let oracle_app: FakeStrideOracle = + FakeStrideOracle::new("fake-stride-oracle", mock.chain(OSMOSIS)?.clone()); + oracle_app.upload()?; + let init_msg = FakeStrideOracleInstantiateMsg { + redemption_rate: Decimal::percent(REDEMPTION_RATE_PERCENT), + }; + oracle_app.instantiate(&init_msg, None, None)?; + + origin_account.install_app( + &app, + &LstAdapterInstantiateMsg { + owner: owner.to_string(), + denoms: Denoms { + lst: LST_DENOM.to_string(), + underlying: DENOM.to_string(), + }, + vault: vault.to_string(), + observer: vault.to_string(), + stride_oracle: oracle_app.addr_str()?, + unbond_period_secs: UNBOND_PERIOD, + }, + None, + )?; + + let interchain_channel = mock.create_channel( + OSMOSIS, + STARGAZE, + &PortId::transfer(), + &PortId::transfer(), + "ics20-1", + None, // Unordered channel + )?; + + abstract_interface::ExecuteMsgFns::update_channels( + &abstr_origin.ans_host, + vec![( + UncheckedChannelEntry { + connected_chain: "stargaze".to_string(), + protocol: ICS20.to_string(), + }, + interchain_channel + .interchain_channel + .get_chain(OSMOSIS)? + .channel + .unwrap() + .to_string(), + )], + vec![], + )?; + + abstract_interface::ExecuteMsgFns::update_channels( + &abstr_remote.ans_host, + vec![( + UncheckedChannelEntry { + connected_chain: "juno".to_string(), + protocol: ICS20.to_string(), + }, + interchain_channel + .interchain_channel + .get_chain(STARGAZE)? + .channel + .unwrap() + .to_string(), + )], + vec![], + )?; + Ok(TestEnv { + app, + mock, + origin_account, + remote_account_id, + abstr_remote, + oracle_app, + }) +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/src/tests/mod.rs b/smart-contracts/contracts/lst-adapter-osmosis/src/tests/mod.rs new file mode 100644 index 000000000..d306d145a --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/src/tests/mod.rs @@ -0,0 +1,4 @@ +mod fake_stride_oracle; +mod ibc_setup; +mod unbond; +mod update; diff --git a/smart-contracts/contracts/lst-adapter-osmosis/src/tests/unbond.rs b/smart-contracts/contracts/lst-adapter-osmosis/src/tests/unbond.rs new file mode 100644 index 000000000..c82a1659f --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/src/tests/unbond.rs @@ -0,0 +1,395 @@ +use crate::msg::{LstAdapterExecuteMsgFns, LstAdapterQueryMsgFns}; +use crate::state::{UnbondInfo, UnbondStatus}; +use crate::tests::fake_stride_oracle::FakeStrideOracleExecuteMsgFns; +use crate::tests::ibc_setup::{ + create_app, DENOM, LST_DENOM, OSMOSIS, REDEMPTION_RATE_PERCENT, STARGAZE, UNBOND_PERIOD, +}; +use crate::LstAdapterError; +use abstract_interface::AbstractAccount; +use cosmwasm_std::{coins, Decimal, Uint128}; +use cw_orch::{anyhow, prelude::*}; +use cw_orch_interchain::prelude::*; +use quasar_types::error::FundsError; + +#[test] +fn test_if_not_vault_then_unbond_fails() -> anyhow::Result<()> { + let app = create_app(vec![], Some("other".to_string()))?.app; + + let result = app.unbond(&[]); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().downcast::()?, + LstAdapterError::NotVault {} + ); + Ok(()) +} + +#[test] +fn test_if_missing_funds_then_unbond_fails() -> anyhow::Result<()> { + let app = create_app(vec![], None)?.app; + + let result = app.unbond(&[]); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().downcast::()?, + LstAdapterError::Funds(FundsError::InvalidAssets(1)) + ); + Ok(()) +} + +#[test] +fn test_if_wrong_denom_then_unbond_fails() -> anyhow::Result<()> { + let funds = coins(123, "wrong"); + let app = create_app(funds.clone(), None)?.app; + + let result = app.unbond(&funds); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().downcast::()?, + LstAdapterError::Funds(FundsError::WrongDenom(LST_DENOM.to_string())) + ); + Ok(()) +} + +#[test] +fn test_unbond_sends_ibc_message() -> anyhow::Result<()> { + let funds = coins(123, LST_DENOM); + let env = create_app(funds.clone(), None)?; + let app = env.app; + + let ibc_action_result = app.unbond(&funds)?; + let _ = env.mock.wait_ibc(OSMOSIS, ibc_action_result)?; + + let remote_account = AbstractAccount::new(&env.abstr_remote, env.remote_account_id.clone()); + let remote_denom: &str = &format!("ibc/channel-0/{}", LST_DENOM); + let remote_balance = env + .mock + .chain(STARGAZE)? + .query_balance(&remote_account.proxy.address()?, remote_denom)?; + assert_eq!(Uint128::from(123u32), remote_balance); + + let expected_pending = + Uint128::from(123u32).checked_mul_floor(Decimal::percent(REDEMPTION_RATE_PERCENT))?; + let pending_unbonds = app.pending_unbonds()?; + let start_time = env.mock.chain(OSMOSIS)?.block_info()?.time; + assert_eq!(pending_unbonds.len(), 1); + assert_eq!( + pending_unbonds[0], + UnbondInfo { + amount: expected_pending, + unbond_start: start_time, + status: UnbondStatus::Unconfirmed + } + ); + + let balance = app.balance_in_underlying()?; + assert_eq!(balance, expected_pending); + + Ok(()) +} + +#[test] +fn test_unbonding_delayed_if_previous_unbond_was_not_confirmed() -> anyhow::Result<()> { + let sender_balance = coins(123_456_789, LST_DENOM); + let env = create_app(sender_balance, None)?; + let app = env.app; + let osmosis = env.mock.chain(OSMOSIS)?; + + let amount0 = Uint128::from(123u128); + let amount1 = Uint128::from(345u128); + let amount2 = Uint128::from(567u128); + let total = amount0 + amount1 + amount2; + let redemption_rate = Decimal::percent(REDEMPTION_RATE_PERCENT); + let expected_redeem_amount0 = amount0 * redemption_rate; + let funds = coins(amount0.u128(), LST_DENOM); + let ibc_action_result = app.unbond(&funds)?; + let _ = env.mock.wait_ibc(OSMOSIS, ibc_action_result)?; + + let funds = coins(amount1.u128(), LST_DENOM); + assert!(app.unbond(&funds).is_ok()); + let contract_balance = osmosis.query_balance(&app.address()?, LST_DENOM)?; + assert_eq!(contract_balance, amount1); + let total_app_balance = app.balance_in_underlying()?; + let expected_total = expected_redeem_amount0 + amount1 * redemption_rate; + assert_eq!(total_app_balance, expected_total); + + let new_redemption_rate = Decimal::percent(200); + env.oracle_app.update(123, new_redemption_rate)?; + + assert!(app.confirm_unbond(expected_redeem_amount0).is_ok()); + let funds = coins(amount2.u128(), LST_DENOM); + let ibc_action_result = app.unbond(&funds)?; + let _ = env.mock.wait_ibc(OSMOSIS, ibc_action_result)?; + let contract_balance = osmosis.query_balance(&app.address()?, LST_DENOM)?; + assert_eq!(contract_balance, Uint128::zero()); + let total_app_balance = app.balance_in_underlying()?; + let expected_total = + expected_redeem_amount0 + amount1 * new_redemption_rate + amount2 * new_redemption_rate; + assert_eq!(total_app_balance, expected_total); + + let remote_account = AbstractAccount::new(&env.abstr_remote, env.remote_account_id.clone()); + let remote_denom: &str = &format!("ibc/channel-0/{}", LST_DENOM); + let remote_balance = env + .mock + .chain(STARGAZE)? + .query_balance(&remote_account.proxy.address()?, remote_denom)?; + assert_eq!(remote_balance, total); + + Ok(()) +} + +#[test] +fn test_multiple_pending_unbonds() -> anyhow::Result<()> { + let sender_balance = coins(123_456_789, LST_DENOM); + let env = create_app(sender_balance, None)?; + let app = env.app; + + let amount0 = Uint128::from(123u128); + let amount1 = Uint128::from(345u128); + let amount2 = Uint128::from(789u128); + let redemption_rate = Decimal::percent(REDEMPTION_RATE_PERCENT); + let offset_secs = 500u64; + let start_time = env.mock.chain(OSMOSIS)?.block_info()?.time; + let funds = coins(amount0.u128(), LST_DENOM); + let ibc_action_result = app.unbond(&funds)?; + let _ = env.mock.wait_ibc(OSMOSIS, ibc_action_result)?; + + env.mock.chain(OSMOSIS)?.wait_seconds(offset_secs)?; + let expected_redeem_amount0 = Uint128::from(10u64) + amount0 * redemption_rate; + assert!(app.confirm_unbond(expected_redeem_amount0).is_ok()); + + let funds = coins(amount1.u128(), LST_DENOM); + let ibc_action_result = app.unbond(&funds)?; + let _ = env.mock.wait_ibc(OSMOSIS, ibc_action_result)?; + + env.mock.chain(OSMOSIS)?.wait_seconds(offset_secs)?; + assert!(app.confirm_unbond(amount1 * redemption_rate).is_ok()); + + let funds = coins(amount2.u128(), LST_DENOM); + let ibc_action_result = app.unbond(&funds)?; + let _ = env.mock.wait_ibc(OSMOSIS, ibc_action_result)?; + let total_redeemed = + expected_redeem_amount0 + amount1 * redemption_rate + amount2 * redemption_rate; + + let pending_unbonds = app.pending_unbonds()?; + assert_eq!(pending_unbonds.len(), 3); + assert_eq!( + pending_unbonds[0], + UnbondInfo { + amount: expected_redeem_amount0, + unbond_start: start_time, + status: UnbondStatus::Confirmed + } + ); + assert_eq!( + pending_unbonds[1], + UnbondInfo { + amount: amount1 * redemption_rate, + unbond_start: start_time.plus_seconds(offset_secs), + status: UnbondStatus::Confirmed + } + ); + assert_eq!( + pending_unbonds[2], + UnbondInfo { + amount: amount2 * redemption_rate, + unbond_start: start_time.plus_seconds(2 * offset_secs), + status: UnbondStatus::Unconfirmed + } + ); + + let balance = app.balance_in_underlying()?; + assert_eq!(balance, total_redeemed); + + Ok(()) +} + +#[test] +fn test_claim_multiple_deposits_and_random_donation() -> anyhow::Result<()> { + let sender_balance = coins(123_456_789, LST_DENOM); + let env = create_app(sender_balance, None)?; + let app = env.app; + + let amount0 = Uint128::from(123u128); + let amount1 = Uint128::from(345u128); + let donation = Uint128::from(567u128); + let redemption_rate = Decimal::percent(REDEMPTION_RATE_PERCENT); + let osmosis = env.mock.chain(OSMOSIS)?; + let start_time = osmosis.block_info()?.time; + let funds = coins(amount0.u128(), LST_DENOM); + let ibc_action_result = app.unbond(&funds)?; + let _ = env.mock.wait_ibc(OSMOSIS, ibc_action_result)?; + assert!(app.confirm_unbond(amount0 * redemption_rate).is_ok()); + + osmosis.wait_seconds(UNBOND_PERIOD)?; + + let funds = coins(amount1.u128(), LST_DENOM); + let ibc_action_result = app.unbond(&funds)?; + let _ = env.mock.wait_ibc(OSMOSIS, ibc_action_result)?; + + let redeemable0 = amount0 * redemption_rate; + let redeemable1 = amount1 * redemption_rate; + let total_redeemable = redeemable0 + redeemable1; + + let balance = app.balance_in_underlying()?; + assert_eq!(balance, total_redeemable); + + let claimable = app.claimable()?; + assert_eq!(claimable.amount, Uint128::zero()); + + let result = app.claim(); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().downcast::()?, + LstAdapterError::NothingToClaim {} + ); + osmosis.set_balance( + &app.address()?, + coins((donation + redeemable0).into(), DENOM), + )?; + let claimable = app.claimable()?; + assert_eq!(claimable.amount, donation); + assert!(app.confirm_unbond_finished(start_time).is_ok()); + let cb = osmosis.query_balance(&app.address()?, DENOM)?; + assert_eq!(cb, redeemable0 + donation); + let pending = app.pending_unbonds()?; + assert_eq!(pending.len(), 1); + assert_eq!( + pending[0], + UnbondInfo { + amount: redeemable1, + unbond_start: osmosis.block_info()?.time, + status: UnbondStatus::Unconfirmed + } + ); + let claimable = app.claimable()?; + assert_eq!(claimable.amount, redeemable0 + donation); + + let expected_contract_balance = redeemable0 + redeemable1; + assert_eq!(app.balance_in_underlying()?, expected_contract_balance); + + assert!(app.claim().is_ok()); + let claimed = osmosis.query_balance(&osmosis.sender(), DENOM)?; + assert_eq!(claimed, redeemable0 + donation); + + assert_eq!(app.balance_in_underlying()?, redeemable1); + + Ok(()) +} + +#[test] +fn test_claim_works_unbond_is_finished_and_funds_are_available() -> anyhow::Result<()> { + let sender_balance = coins(123_456_789, LST_DENOM); + let env = create_app(sender_balance, None)?; + let app = env.app; + + let amount0 = Uint128::from(123u128); + let redemption_rate = Decimal::percent(REDEMPTION_RATE_PERCENT); + let osmosis = env.mock.chain(OSMOSIS)?; + let start_time = osmosis.block_info()?.time; + let funds = coins(amount0.u128(), LST_DENOM); + let ibc_action_result = app.unbond(&funds)?; + let _ = env.mock.wait_ibc(OSMOSIS, ibc_action_result)?; + assert!(app.confirm_unbond(amount0 * redemption_rate).is_ok()); + + osmosis.wait_seconds(UNBOND_PERIOD)?; + + let total_redeem_amount = amount0 * redemption_rate; + + let underlying_balance = total_redeem_amount; + osmosis.set_balance(&app.address()?, coins(underlying_balance.into(), DENOM))?; + assert!(app.confirm_unbond_finished(start_time).is_ok()); + + assert_eq!(app.balance_in_underlying()?, total_redeem_amount); + + assert!(app.claim().is_ok()); + let balance = osmosis.query_balance(&osmosis.sender(), DENOM)?; + assert_eq!(balance, underlying_balance); + + let expected_contract_balance = Uint128::zero(); + assert_eq!(app.balance_in_underlying()?, expected_contract_balance); + + Ok(()) +} + +#[test] +fn test_confirm_finished_fails_before_expiration() -> anyhow::Result<()> { + let sender_balance = coins(123_456_789, LST_DENOM); + let env = create_app(sender_balance, None)?; + let app = env.app; + + let amount0 = Uint128::from(123u128); + let redemption_rate = Decimal::percent(REDEMPTION_RATE_PERCENT); + let osmosis = env.mock.chain(OSMOSIS)?; + let start_time = osmosis.block_info()?.time; + let funds = coins(amount0.u128(), LST_DENOM); + let ibc_action_result = app.unbond(&funds)?; + let _ = env.mock.wait_ibc(OSMOSIS, ibc_action_result)?; + assert!(app.confirm_unbond(amount0 * redemption_rate).is_ok()); + osmosis.wait_seconds(UNBOND_PERIOD / 2)?; + let result = app.confirm_unbond_finished(start_time); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().downcast::()?, + LstAdapterError::UnbondNotFinished {} + ); + + Ok(()) +} +#[test] +fn test_confirm_finished_fails_if_funds_are_not_yet_available() -> anyhow::Result<()> { + let sender_balance = coins(123_456_789, LST_DENOM); + let env = create_app(sender_balance, None)?; + let app = env.app; + + let amount0 = Uint128::from(123u128); + let redemption_rate = Decimal::percent(REDEMPTION_RATE_PERCENT); + let osmosis = env.mock.chain(OSMOSIS)?; + let start_time = osmosis.block_info()?.time; + let funds = coins(amount0.u128(), LST_DENOM); + let ibc_action_result = app.unbond(&funds)?; + let _ = env.mock.wait_ibc(OSMOSIS, ibc_action_result)?; + assert!(app.confirm_unbond(amount0 * redemption_rate).is_ok()); + osmosis.wait_seconds(UNBOND_PERIOD)?; + + let result = app.confirm_unbond_finished(start_time); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().downcast::()?, + LstAdapterError::StillWaitingForFunds {} + ); + + Ok(()) +} + +#[test] +fn test_claim_fails_unbond_is_not_finished_and_funds_are_available() -> anyhow::Result<()> { + let sender_balance = coins(123_456_789, LST_DENOM); + let env = create_app(sender_balance, None)?; + let app = env.app; + + let amount0 = Uint128::from(123u128); + let redemption_rate = Decimal::percent(REDEMPTION_RATE_PERCENT); + let osmosis = env.mock.chain(OSMOSIS)?; + let funds = coins(amount0.u128(), LST_DENOM); + let ibc_action_result = app.unbond(&funds)?; + let _ = env.mock.wait_ibc(OSMOSIS, ibc_action_result)?; + assert!(app.confirm_unbond(amount0 * redemption_rate).is_ok()); + + osmosis.wait_seconds(UNBOND_PERIOD)?; + + let total_redeem_amount = amount0 * redemption_rate; + + osmosis.set_balance(&app.address()?, coins(total_redeem_amount.into(), DENOM))?; + + assert_eq!(app.balance_in_underlying()?, total_redeem_amount); + + let result = app.claim(); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().downcast::()?, + LstAdapterError::NothingToClaim {} + ); + + Ok(()) +} diff --git a/smart-contracts/contracts/lst-adapter-osmosis/src/tests/update.rs b/smart-contracts/contracts/lst-adapter-osmosis/src/tests/update.rs new file mode 100644 index 000000000..779f3ad47 --- /dev/null +++ b/smart-contracts/contracts/lst-adapter-osmosis/src/tests/update.rs @@ -0,0 +1,142 @@ +use crate::msg::{LstAdapterExecuteMsgFns, LstAdapterQueryMsgFns}; +use crate::state::{Denoms, IbcConfig}; +use crate::tests::ibc_setup::{create_app, DENOM, LST_DENOM, OSMOSIS}; +use crate::LstAdapterError; +use cw_orch::anyhow; +use cw_orch::contract::interface_traits::CallAs; +use cw_orch_interchain::InterchainEnv; + +#[test] +fn test_only_owner_can_update_ibc_config() -> anyhow::Result<()> { + let env = create_app(vec![], None)?; + let mut app = env.app; + + let remote_chain = "stride".to_string(); + let revision = Some(1u64); + let block_offset = Some(2u64); + let timeout_secs = Some(3u64); + let channel = "channel-123".to_string(); + + let other_sender = env.mock.chain(OSMOSIS)?.addr_make("other_sender"); + app.set_sender(&other_sender); + let result = app.update_ibc_config( + remote_chain.clone(), + channel.clone(), + block_offset, + revision, + timeout_secs, + ); + + assert_eq!( + result.unwrap_err().downcast::().unwrap(), + LstAdapterError::Owner(mars_owner::OwnerError::NotOwner {}) + ); + Ok(()) +} + +#[test] +fn test_update_ibc_config() -> anyhow::Result<()> { + let app = create_app(vec![], None)?.app; + + let result = app.ibc_config()?; + assert_eq!(result, IbcConfig::default()); + + let remote_chain = "stride".to_string(); + let revision = Some(1u64); + let block_offset = Some(2u64); + let timeout_secs = Some(3u64); + let channel = "channel-123".to_string(); + + assert!(app + .update_ibc_config( + channel.clone(), + remote_chain.clone(), + block_offset, + revision, + timeout_secs + ) + .is_ok()); + + let result = app.ibc_config()?; + assert_eq!( + result, + IbcConfig { + remote_chain, + channel, + revision, + block_offset, + timeout_secs, + } + ); + Ok(()) +} + +#[test] +fn test_only_owner_can_update() -> anyhow::Result<()> { + let env = create_app(vec![], None)?; + let mut app = env.app; + + let other_sender = env.mock.chain(OSMOSIS)?.addr_make("other_sender"); + app.set_sender(&other_sender); + let result = app.update(None, None, None, None, None); + + assert_eq!( + result.unwrap_err().downcast::().unwrap(), + LstAdapterError::Owner(mars_owner::OwnerError::NotOwner {}) + ); + Ok(()) +} + +#[test] +fn test_update() -> anyhow::Result<()> { + let env = create_app(vec![], None)?; + let app = env.app; + + assert_eq!(app.denoms()?.lst, LST_DENOM); + let other_denom = "other_denom".to_string(); + assert!(app + .update( + Some(Denoms { + lst: other_denom.clone(), + underlying: DENOM.to_string() + }), + None, + None, + None, + None + ) + .is_ok()); + assert_eq!(app.denoms()?.lst, other_denom); + + let new_vault = env + .mock + .chain(OSMOSIS)? + .addr_make("other_vault") + .to_string(); + assert!(app + .update(None, None, None, None, Some(new_vault.clone())) + .is_ok()); + assert_eq!(app.vault()?, new_vault); + + let other_denom = "even_another_denom".to_string(); + let new_vault = env + .mock + .chain(OSMOSIS)? + .addr_make("even_another_vault") + .to_string(); + assert!(app + .update( + Some(Denoms { + lst: other_denom.clone(), + underlying: DENOM.to_string() + }), + None, + None, + None, + Some(new_vault.clone()) + ) + .is_ok()); + assert_eq!(app.denoms()?.lst, other_denom); + assert_eq!(app.vault()?, new_vault); + Ok(()) +} diff --git a/smart-contracts/contracts/lst-dex-adapter-osmosis/.cargo/config.toml b/smart-contracts/contracts/lst-dex-adapter-osmosis/.cargo/config.toml index 116bc2371..22db5d77a 100644 --- a/smart-contracts/contracts/lst-dex-adapter-osmosis/.cargo/config.toml +++ b/smart-contracts/contracts/lst-dex-adapter-osmosis/.cargo/config.toml @@ -3,6 +3,4 @@ wasm = "build --release --target wasm32-unknown-unknown" schema = "run --bin schema --features schema" lint = "clippy -- -D warnings" lint-fix = "clippy --fix -- -D warnings" -unit-test = "test --lib" -test-vault = "test --lib -p basic-vault" -clip = "clippy -- --D warnings --A deprecated" \ No newline at end of file +unit-test = "test" \ No newline at end of file diff --git a/smart-contracts/contracts/lst-dex-adapter-osmosis/Cargo.toml b/smart-contracts/contracts/lst-dex-adapter-osmosis/Cargo.toml index 3c1117b7a..0c83f15ca 100644 --- a/smart-contracts/contracts/lst-dex-adapter-osmosis/Cargo.toml +++ b/smart-contracts/contracts/lst-dex-adapter-osmosis/Cargo.toml @@ -51,6 +51,7 @@ dotenv = { workspace = true, optional = true } env_logger = { workspace = true, optional = true } abstract-dex-adapter = { git = "https://github.com/AbstractSDK/abstract.git", tag="v0.22.1", default-features = false } quasar-types = { workspace = true } +lst-adapter-osmosis = { workspace = true } [dev-dependencies] abstract-client = { workspace = true } diff --git a/smart-contracts/contracts/lst-dex-adapter-osmosis/README.md b/smart-contracts/contracts/lst-dex-adapter-osmosis/README.md index e6ad705c4..e073fee6e 100644 --- a/smart-contracts/contracts/lst-dex-adapter-osmosis/README.md +++ b/smart-contracts/contracts/lst-dex-adapter-osmosis/README.md @@ -3,4 +3,4 @@ Does swaps into an LST through a configured pool if the price is below the LST redemption rate. ### schema -For the generation of the schema json files execute: `cargo run --bin schema --features schema` \ No newline at end of file +For the generation of the schema json files execute: `cargo run --bin schema --features schema` diff --git a/smart-contracts/contracts/lst-dex-adapter-osmosis/src/handlers/execute.rs b/smart-contracts/contracts/lst-dex-adapter-osmosis/src/handlers/execute.rs index 44b63b803..436df62f4 100644 --- a/smart-contracts/contracts/lst-dex-adapter-osmosis/src/handlers/execute.rs +++ b/smart-contracts/contracts/lst-dex-adapter-osmosis/src/handlers/execute.rs @@ -9,7 +9,7 @@ use crate::{ use abstract_app::{sdk::TransferInterface, traits::AbstractResponse}; use cosmwasm_std::{Decimal, DepsMut, Env, MessageInfo, SubMsg}; use cw_asset::Asset; -use quasar_types::lst_adapter::{QueryMsg as LstQueryMsg, RedemptionRate}; +use lst_adapter_osmosis::msg::LstAdapterQueryMsg; use quasar_types::{abstract_sdk::QueryMsg as AbstractQueryMsg, error::assert_funds_single_token}; pub fn execute_handler( @@ -43,13 +43,10 @@ fn swap( state.receive_asset.clone(), state.pool.clone(), )?; - let redemption_rate = deps - .querier - .query_wasm_smart::( - state.lst_adapter, - &AbstractQueryMsg::Module(LstQueryMsg::RedemptionRate {}), - )? - .redemption_rate; + let redemption_rate = deps.querier.query_wasm_smart::( + state.lst_adapter, + &AbstractQueryMsg::Module(LstAdapterQueryMsg::RedemptionRate {}), + )?; let price = Decimal::from_ratio(offer_amount, simulated.return_amount); if price.checked_mul( Decimal::one() diff --git a/smart-contracts/contracts/lst-dex-adapter-osmosis/tests/fake_lst_adapter/mod.rs b/smart-contracts/contracts/lst-dex-adapter-osmosis/tests/fake_lst_adapter/mod.rs index 64c3098ef..721df5123 100644 --- a/smart-contracts/contracts/lst-dex-adapter-osmosis/tests/fake_lst_adapter/mod.rs +++ b/smart-contracts/contracts/lst-dex-adapter-osmosis/tests/fake_lst_adapter/mod.rs @@ -8,7 +8,6 @@ use cosmwasm_std::StdError; use cosmwasm_std::{to_json_binary, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Response}; use cw_asset::AssetError; use cw_storage_plus::Item; -use quasar_types::lst_adapter::RedemptionRate; use thiserror::Error; #[cfg(not(target_arch = "wasm32"))] @@ -102,9 +101,7 @@ pub fn query_( msg: FakeLstQueryMsg, ) -> Result { match msg { - FakeLstQueryMsg::RedemptionRate {} => Ok(to_json_binary(&RedemptionRate { - redemption_rate: STATE.load(deps.storage)?, - })?), + FakeLstQueryMsg::RedemptionRate {} => Ok(to_json_binary(&STATE.load(deps.storage)?)?), } } diff --git a/smart-contracts/packages/quasar-types/Cargo.toml b/smart-contracts/packages/quasar-types/Cargo.toml index 344119fab..e7335776e 100644 --- a/smart-contracts/packages/quasar-types/Cargo.toml +++ b/smart-contracts/packages/quasar-types/Cargo.toml @@ -18,6 +18,7 @@ prost = { workspace = true } cw20 = { workspace = true } cosmos-sdk-proto = { workspace = true } serde-json-wasm = { workspace = true } +serde_json = { workspace = true } derive_more = {version = "0.99.17", default-features = false, features = [ "display", diff --git a/smart-contracts/packages/quasar-types/src/ibc.rs b/smart-contracts/packages/quasar-types/src/ibc.rs index 737017e1d..ddbdc5d7c 100644 --- a/smart-contracts/packages/quasar-types/src/ibc.rs +++ b/smart-contracts/packages/quasar-types/src/ibc.rs @@ -68,9 +68,9 @@ pub struct MsgTransfer { #[serde(rename_all = "snake_case")] pub struct Height { #[prost(uint64, optional, tag = "1")] - revision_number: Option, + pub revision_number: Option, #[prost(uint64, optional, tag = "2")] - revision_height: Option, + pub revision_height: Option, } #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Message)] diff --git a/smart-contracts/packages/quasar-types/src/lib.rs b/smart-contracts/packages/quasar-types/src/lib.rs index 7c4ac169d..ac5fb0b86 100644 --- a/smart-contracts/packages/quasar-types/src/lib.rs +++ b/smart-contracts/packages/quasar-types/src/lib.rs @@ -5,8 +5,9 @@ pub mod error; pub mod ibc; pub mod ica; pub mod icq; -pub mod lst_adapter; +pub mod query; pub mod queue; +pub mod stride; pub mod traits; pub mod types; diff --git a/smart-contracts/packages/quasar-types/src/lst_adapter.rs b/smart-contracts/packages/quasar-types/src/lst_adapter.rs deleted file mode 100644 index 1eabd9301..000000000 --- a/smart-contracts/packages/quasar-types/src/lst_adapter.rs +++ /dev/null @@ -1,49 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Decimal, Uint128}; - -#[cw_serde] -pub struct InstantiateMsg { - pub owner: String, - pub lst_denom: String, -} - -#[cw_serde] -pub enum QueryMsg { - Admin {}, - Config {}, - Pending {}, - RedemptionRate {}, - Claimable {}, -} - -#[cw_serde] -pub enum ExecuteMsg { - Unbond {}, - Claim {}, -} - -#[cw_serde] -pub struct MigrateMsg {} - -#[cw_serde] -pub struct Admin { - pub admin: String, -} - -#[cw_serde] -pub struct Config {} - -#[cw_serde] -pub struct Pending { - pub pending: Uint128, -} - -#[cw_serde] -pub struct Claimable { - pub claimable: Uint128, -} - -#[cw_serde] -pub struct RedemptionRate { - pub redemption_rate: Decimal, -} diff --git a/smart-contracts/packages/quasar-types/src/query.rs b/smart-contracts/packages/quasar-types/src/query.rs new file mode 100644 index 000000000..a00774ba1 --- /dev/null +++ b/smart-contracts/packages/quasar-types/src/query.rs @@ -0,0 +1,13 @@ +use cosmwasm_std::{Addr, Env, QuerierWrapper, StdResult, Uint128}; + +pub fn query_balance(querier: &QuerierWrapper<'_>, addr: &Addr, denom: &str) -> StdResult { + Ok(querier.query_balance(addr, denom)?.amount) +} + +pub fn query_contract_balance( + querier: &QuerierWrapper<'_>, + env: &Env, + denom: &str, +) -> StdResult { + query_balance(querier, &env.contract.address, denom) +} diff --git a/smart-contracts/packages/quasar-types/src/stride.rs b/smart-contracts/packages/quasar-types/src/stride.rs new file mode 100644 index 000000000..8b35ec514 --- /dev/null +++ b/smart-contracts/packages/quasar-types/src/stride.rs @@ -0,0 +1,113 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Empty; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub enum AutopilotStakeAction { + LiquidStake, + RedeemStake, +} + +#[cw_serde] +pub struct AutopilotStakeIbc { + pub action: AutopilotStakeAction, + pub ibc_receiver: String, +} + +#[cw_serde] +#[serde(untagged)] +pub enum AutopilotAction { + Stakeibc { + receiver: String, + stakeibc: AutopilotStakeIbc, + }, + Claim { + receiver: String, + claim: Empty, + }, +} + +#[cw_serde] +pub struct AutopilotMsg { + pub autopilot: AutopilotAction, +} + +pub enum Action { + LiquidStake, + RedeemStake, + Claim, +} + +const MISSING_IBC_RECEIVER_MSG: &str = "Action requires ibc receiver"; + +pub fn get_autopilot_msg( + action: Action, + receiver: &str, + ibc_receiver: Option, +) -> AutopilotMsg { + let autopilot = match action { + Action::LiquidStake => AutopilotAction::Stakeibc { + receiver: receiver.to_string(), + stakeibc: AutopilotStakeIbc { + action: AutopilotStakeAction::LiquidStake, + ibc_receiver: ibc_receiver.expect(MISSING_IBC_RECEIVER_MSG), + }, + }, + Action::RedeemStake => AutopilotAction::Stakeibc { + receiver: receiver.to_string(), + stakeibc: AutopilotStakeIbc { + action: AutopilotStakeAction::RedeemStake, + ibc_receiver: ibc_receiver.expect(MISSING_IBC_RECEIVER_MSG), + }, + }, + Action::Claim => AutopilotAction::Claim { + receiver: receiver.to_string(), + claim: Empty::default(), + }, + }; + AutopilotMsg { autopilot } +} + +#[cfg(test)] +mod tests { + use super::*; + + const RECEIVER: &str = "stride123"; + const IBC_RECEIVER: &str = "osmo456"; + + #[test] + fn test_autopilot_redeem_stake_msg() { + let msg = get_autopilot_msg( + Action::RedeemStake, + RECEIVER, + Some(IBC_RECEIVER.to_string()), + ); + + let expected = format!("{{\"autopilot\":{{\"receiver\":\"{}\",\"stakeibc\":{{\"action\":\"RedeemStake\",\"ibc_receiver\":\"{}\"}}}}}}", RECEIVER, IBC_RECEIVER); + assert_eq!(serde_json::to_string(&msg).unwrap(), expected); + } + + #[test] + fn test_autopilot_liquid_stake_msg() { + let msg = get_autopilot_msg( + Action::LiquidStake, + RECEIVER, + Some(IBC_RECEIVER.to_string()), + ); + + let expected = format!("{{\"autopilot\":{{\"receiver\":\"{}\",\"stakeibc\":{{\"action\":\"LiquidStake\",\"ibc_receiver\":\"{}\"}}}}}}", RECEIVER, IBC_RECEIVER); + assert_eq!(serde_json::to_string(&msg).unwrap(), expected); + } + + #[test] + fn test_autopilot_claim_msg() { + let msg = get_autopilot_msg(Action::Claim, RECEIVER, None); + + let expected = format!( + "{{\"autopilot\":{{\"receiver\":\"{}\",\"claim\":{{}}}}}}", + RECEIVER + ); + assert_eq!(serde_json::to_string(&msg).unwrap(), expected); + } +}